Completed
Push — master ( fdf3c3...41ecec )
by Lars
01:46
created

DB::set_charset()   B

Complexity

Conditions 8
Paths 12

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 10.0155

Importance

Changes 0
Metric Value
dl 0
loc 42
ccs 13
cts 19
cp 0.6842
rs 8.0035
c 0
b 0
f 0
cc 8
nc 12
nop 1
crap 10.0155
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 $mysqli_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
   * @var null|int
129
   */
130
  private $affected_rows;
131
132
  /**
133
   * __construct()
134
   *
135
   * @param string $hostname
136
   * @param string $username
137
   * @param string $password
138
   * @param string $database
139
   * @param int    $port
140
   * @param string $charset
141
   * @param bool   $exit_on_error         <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
142
   *                                      Use false to disable it.</p>
143
   * @param bool   $echo_on_error         <p>Echo the error if "checkForDev()" returns true.
144
   *                                      Use false to disable it.</p>
145
   * @param string $logger_class_name
146
   * @param string $logger_level          <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
147
   * @param array  $extra_config          <p>
148
   *                                      'session_to_db' => bool<br>
149
   *                                      'socket'        => 'string (path)'<br>
150
   *                                      'ssl'           => bool<br>
151
   *                                      'clientkey'     => 'string (path)'<br>
152
   *                                      'clientcert'    => 'string (path)'<br>
153
   *                                      'cacert'        => 'string (path)'<br>
154
   *                                      </p>
155
   */
156 23
  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 = [])
157
  {
158 23
    $this->debug = new Debug($this);
159
160 23
    $this->_loadConfig(
161 23
        $hostname,
162 23
        $username,
163 23
        $password,
164 23
        $database,
165 23
        $port,
166 23
        $charset,
167 23
        $exit_on_error,
168 23
        $echo_on_error,
169 23
        $logger_class_name,
170 23
        $logger_level,
171 23
        $extra_config
172
    );
173
174 14
    $this->connect();
175
176 5
    $this->mysqlDefaultTimeFunctions = [
177
      // Returns the current date.
178
      'CURDATE()',
179
      // CURRENT_DATE	| Synonyms for CURDATE()
180
      'CURRENT_DATE()',
181
      // CURRENT_TIME	| Synonyms for CURTIME()
182
      'CURRENT_TIME()',
183
      // CURRENT_TIMESTAMP | Synonyms for NOW()
184
      'CURRENT_TIMESTAMP()',
185
      // Returns the current time.
186
      'CURTIME()',
187
      // Synonym for NOW()
188
      'LOCALTIME()',
189
      // Synonym for NOW()
190
      'LOCALTIMESTAMP()',
191
      // Returns the current date and time.
192
      'NOW()',
193
      // Returns the time at which the function executes.
194
      'SYSDATE()',
195
      // Returns a UNIX timestamp.
196
      'UNIX_TIMESTAMP()',
197
      // Returns the current UTC date.
198
      'UTC_DATE()',
199
      // Returns the current UTC time.
200
      'UTC_TIME()',
201
      // Returns the current UTC date and time.
202
      'UTC_TIMESTAMP()',
203
    ];
204 5
  }
205
206
  /**
207
   * Prevent the instance from being cloned.
208
   *
209
   * @return void
210
   */
211
  private function __clone()
212
  {
213
  }
214
215
  /**
216
   * __destruct
217
   */
218
  public function __destruct()
219
  {
220
    // close the connection only if we don't save PHP-SESSION's in DB
221
    if ($this->session_to_db === false) {
222
      $this->close();
223
    }
224
  }
225
226
  /**
227
   * @param null|string $sql
228
   * @param array       $bindings
229
   *
230
   * @return bool|int|Result|DB           <p>
231
   *                                      "DB" by "$sql" === null<br />
232
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
233
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
234
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
235
   *                                      "true" by e.g. "DROP"-queries<br />
236
   *                                      "false" on error
237
   *                                      </p>
238
   */
239 4
  public function __invoke(string $sql = null, array $bindings = [])
240
  {
241 4
    return null !== $sql ? $this->query($sql, $bindings) : $this;
242
  }
243
244
  /**
245
   * __wakeup
246
   *
247
   * @return void
248
   */
249 4
  public function __wakeup()
250
  {
251 4
    $this->reconnect();
252 4
  }
253
254
  /**
255
   * Load the config from the constructor.
256
   *
257
   * @param string $hostname
258
   * @param string $username
259
   * @param string $password
260
   * @param string $database
261
   * @param int    $port                  <p>default is (int)3306</p>
262
   * @param string $charset               <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
263
   * @param bool   $exit_on_error         <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
264
   *                                      Use false to disable it.</p>
265
   * @param bool   $echo_on_error         <p>Echo the error if "checkForDev()" returns true.
266
   *                                      Use false to disable it.</p>
267
   * @param string $logger_class_name
268
   * @param string $logger_level
269
   * @param array  $extra_config          <p>
270
   *                                      'session_to_db' => false|true<br>
271
   *                                      'socket' => 'string (path)'<br>
272
   *                                      'ssl' => 'bool'<br>
273
   *                                      'clientkey' => 'string (path)'<br>
274
   *                                      'clientcert' => 'string (path)'<br>
275
   *                                      'cacert' => 'string (path)'<br>
276
   *                                      </p>
277
   *
278
   * @return bool
279
   */
280 23
  private function _loadConfig(
281
      string $hostname,
282
      string $username,
283
      string $password,
284
      string $database,
285
      $port,
286
      string $charset,
287
      bool $exit_on_error,
288
      bool $echo_on_error,
289
      string $logger_class_name,
290
      string $logger_level,
291
      array $extra_config = []
292
  ): bool
293
  {
294 23
    $this->hostname = $hostname;
295 23
    $this->username = $username;
296 23
    $this->password = $password;
297 23
    $this->database = $database;
298
299 23
    if ($charset) {
300 23
      $this->charset = $charset;
301
    }
302
303 23
    if ($port) {
304 11
      $this->port = (int)$port;
305
    } else {
306
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
307 13
      $this->port = (int)@ini_get('mysqli.default_port');
308
    }
309
310
    // fallback
311 23
    if (!$this->port) {
312
      $this->port = 3306;
313
    }
314
315 23
    if (!$this->socket) {
316
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
317 23
      $this->socket = @ini_get('mysqli.default_socket');
318
    }
319
320 23
    $this->debug->setExitOnError($exit_on_error);
321 23
    $this->debug->setEchoOnError($echo_on_error);
322
323 23
    $this->debug->setLoggerClassName($logger_class_name);
324 23
    $this->debug->setLoggerLevel($logger_level);
325
326 23
    $this->setConfigExtra($extra_config);
327
328 23
    return $this->showConfigError();
329
  }
330
331
  /**
332
   * Parses arrays with value pairs and generates SQL to use in queries.
333
   *
334
   * @param array  $arrayPair
335
   * @param string $glue <p>This is the separator.</p>
336
   *
337
   * @return string
338
   *
339
   * @internal
340
   */
341 72
  public function _parseArrayPair(array $arrayPair, string $glue = ','): string
342
  {
343
    // init
344 72
    $sql = '';
345
346 72
    if (\count($arrayPair) === 0) {
347
      return '';
348
    }
349
350 72
    $arrayPairCounter = 0;
351 72
    foreach ($arrayPair as $_key => $_value) {
352 72
      $_connector = '=';
353 72
      $_glueHelper = '';
354 72
      $_key_upper = \strtoupper($_key);
355
356 72
      if (\strpos($_key_upper, ' NOT') !== false) {
357 6
        $_connector = 'NOT';
358
      }
359
360 72
      if (\strpos($_key_upper, ' IS') !== false) {
361 3
        $_connector = 'IS';
362
      }
363
364 72
      if (\strpos($_key_upper, ' IS NOT') !== false) {
365 3
        $_connector = 'IS NOT';
366
      }
367
368 72
      if (\strpos($_key_upper, ' IN') !== false) {
369 3
        $_connector = 'IN';
370
      }
371
372 72
      if (\strpos($_key_upper, ' NOT IN') !== false) {
373 3
        $_connector = 'NOT IN';
374
      }
375
376 72
      if (\strpos($_key_upper, ' BETWEEN') !== false) {
377 3
        $_connector = 'BETWEEN';
378
      }
379
380 72
      if (\strpos($_key_upper, ' NOT BETWEEN') !== false) {
381 3
        $_connector = 'NOT BETWEEN';
382
      }
383
384 72
      if (\strpos($_key_upper, ' LIKE') !== false) {
385 6
        $_connector = 'LIKE';
386
      }
387
388 72
      if (\strpos($_key_upper, ' NOT LIKE') !== false) {
389 6
        $_connector = 'NOT LIKE';
390
      }
391
392 72 View Code Duplication
      if (\strpos($_key_upper, ' >') !== false && \strpos($_key_upper, ' =') === false) {
393 8
        $_connector = '>';
394
      }
395
396 72 View Code Duplication
      if (\strpos($_key_upper, ' <') !== false && \strpos($_key_upper, ' =') === false) {
397 3
        $_connector = '<';
398
      }
399
400 72
      if (\strpos($_key_upper, ' >=') !== false) {
401 8
        $_connector = '>=';
402
      }
403
404 72
      if (\strpos($_key_upper, ' <=') !== false) {
405 3
        $_connector = '<=';
406
      }
407
408 72
      if (\strpos($_key_upper, ' <>') !== false) {
409 3
        $_connector = '<>';
410
      }
411
412 72
      if (\strpos($_key_upper, ' OR') !== false) {
413 6
        $_glueHelper = 'OR';
414
      }
415
416 72
      if (\strpos($_key_upper, ' AND') !== false) {
417 3
        $_glueHelper = 'AND';
418
      }
419
420 72
      if (\is_array($_value) === true) {
421 6
        foreach ($_value as $oldKey => $oldValue) {
422 6
          $_value[$oldKey] = $this->secure($oldValue);
423
        }
424
425 6
        if ($_connector === 'NOT IN' || $_connector === 'IN') {
426 3
          $_value = '(' . \implode(',', $_value) . ')';
427 6
        } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
428 6
          $_value = '(' . \implode(' AND ', $_value) . ')';
429
        }
430
431
      } else {
432 72
        $_value = $this->secure($_value);
433
      }
434
435 72
      $quoteString = $this->quote_string(
436 72
          \trim(
437 72
              \str_ireplace(
438
                  [
439 72
                      $_connector,
440 72
                      $_glueHelper,
441
                  ],
442 72
                  '',
443 72
                  $_key
444
              )
445
          )
446
      );
447
448 72
      $_value = (array)$_value;
449
450 72
      if (!$_glueHelper) {
451 72
        $_glueHelper = $glue;
452
      }
453
454 72
      $tmpCounter = 0;
455 72
      foreach ($_value as $valueInner) {
456
457 72
        $_glueHelperInner = $_glueHelper;
458
459 72
        if ($arrayPairCounter === 0) {
460
461 72
          if ($tmpCounter === 0 && $_glueHelper === 'OR') {
462 3
            $_glueHelperInner = '1 = 1 AND ('; // first "OR"-query glue
463 72
          } elseif ($tmpCounter === 0) {
464 72
            $_glueHelperInner = ''; // first query glue e.g. for "INSERT"-query -> skip the first ","
465
          }
466
467 68
        } elseif ($tmpCounter === 0 && $_glueHelper === 'OR') {
468 3
          $_glueHelperInner = 'AND ('; // inner-loop "OR"-query glue
469
        }
470
471 72
        if (\is_string($valueInner) && $valueInner === '') {
472
          $valueInner = "''";
473
        }
474
475 72
        $sql .= ' ' . $_glueHelperInner . ' ' . $quoteString . ' ' . $_connector . ' ' . $valueInner . " \n";
476 72
        $tmpCounter++;
477
      }
478
479 72
      if ($_glueHelper === 'OR') {
480 6
        $sql .= ' ) ';
481
      }
482
483 72
      $arrayPairCounter++;
484
    }
485
486 72
    return $sql;
487
  }
488
489
  /**
490
   * _parseQueryParams
491
   *
492
   * @param string $sql
493
   * @param array  $params
494
   *
495
   * @return array <p>with the keys -> 'sql', 'params'</p>
496
   */
497 17
  private function _parseQueryParams(string $sql, array $params = []): array
498
  {
499 17
    $offset = \strpos($sql, '?');
500
501
    // is there anything to parse?
502
    if (
503 17
        $offset === false
504
        ||
505 17
        \count($params) === 0
506
    ) {
507 13
      return ['sql' => $sql, 'params' => $params];
508
    }
509
510 7
    foreach ($params as $key => $param) {
511
512
      // use this only for not named parameters
513 7
      if (!\is_int($key)) {
514 3
        continue;
515
      }
516
517 7
      if ($offset === false) {
518
        continue;
519
      }
520
521 7
      $replacement = $this->secure($param);
522
523 7
      unset($params[$key]);
524
525 7
      $sql = \substr_replace($sql, $replacement, $offset, 1);
526 7
      $offset = \strpos($sql, '?', $offset + \strlen((string)$replacement));
527
    }
528
529 7
    return ['sql' => $sql, 'params' => $params];
530
  }
531
532
  /**
533
   * Returns the SQL by replacing :placeholders with SQL-escaped values.
534
   *
535
   * @param mixed $sql    <p>The SQL string.</p>
536
   * @param array $params <p>An array of key-value bindings.</p>
537
   *
538
   * @return array <p>with the keys -> 'sql', 'params'</p>
539
   */
540 20
  private function _parseQueryParamsByName(string $sql, array $params = []): array
541
  {
542
    // is there anything to parse?
543
    if (
544 20
        \strpos($sql, ':') === false
545
        ||
546 20
        \count($params) === 0
547
    ) {
548 7
      return ['sql' => $sql, 'params' => $params];
549
    }
550
551 16
    $offset = null;
552 16
    $replacement = null;
553 16
    foreach ($params as $name => $param) {
554
555
      // use this only for named parameters
556 16
      if (\is_int($name)) {
557
        continue;
558
      }
559
560
      // add ":" if needed
561 16
      if (\strpos($name, ':') !== 0) {
562 6
        $nameTmp = ':' . $name;
563
      } else {
564 10
        $nameTmp = $name;
565
      }
566
567 16
      if ($offset === null) {
568 16
        $offset = \strpos($sql, $nameTmp);
569
      } else {
570 15
        $offset = \strpos($sql, $nameTmp, $offset + \strlen((string)$replacement));
571
      }
572
573 16
      if ($offset === false) {
574 3
        continue;
575
      }
576
577 16
      $replacement = $this->secure($param);
578
579 16
      unset($params[$name]);
580
581 16
      $sql = \substr_replace($sql, $replacement, $offset, \strlen($nameTmp));
582
    }
583
584 16
    return ['sql' => $sql, 'params' => $params];
585
  }
586
587
  /**
588
   * Gets the number of affected rows in a previous MySQL operation.
589
   *
590
   * @return int
591
   */
592 28
  public function affected_rows(): int
593
  {
594
    if (
595 28
        $this->mysqli_link
596
        &&
597 28
        $this->mysqli_link instanceof \mysqli
598
    ) {
599 28
      return \mysqli_affected_rows($this->mysqli_link);
600
    }
601
602
    return (int)$this->affected_rows;
603
  }
604
605
  /**
606
   * Begins a transaction, by turning off auto commit.
607
   *
608
   * @return bool <p>This will return true or false indicating success of transaction</p>
609
   */
610 18
  public function beginTransaction(): bool
611
  {
612 18
    if ($this->in_transaction === true) {
613 6
      $this->debug->displayError('Error: mysql server already in transaction!', false);
614
615 6
      return false;
616
    }
617
618 18
    $this->clearErrors(); // needed for "$this->endTransaction()"
619 18
    $this->in_transaction = true;
620
621 18
    if ($this->mysqli_link) {
622 18
      $return = \mysqli_autocommit($this->mysqli_link, false);
623
    } elseif ($this->isDoctrinePDOConnection() === true) {
624
      $this->doctrine_connection->setAutoCommit(false);
625
      $this->doctrine_connection->beginTransaction();
626
627
      if ($this->doctrine_connection->isTransactionActive() === true) {
628
        $return = true;
629
      } else {
630
        $return = false;
631
      }
632
    }
633
634 18
    if ($return === false) {
0 ignored issues
show
Bug introduced by
The variable $return does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
635
      $this->in_transaction = false;
636
    }
637
638 18
    return $return;
639
  }
640
641
  /**
642
   * Clear the errors in "_debug->_errors".
643
   *
644
   * @return bool
645
   */
646 18
  public function clearErrors(): bool
647
  {
648 18
    return $this->debug->clearErrors();
649
  }
650
651
  /**
652
   * Closes a previously opened database connection.
653
   *
654
   * @return bool
655
   *              Will return "true", if the connection was closed,
656
   *              otherwise (e.g. if the connection was already closed) "false".
657
   */
658 6
  public function close(): bool
659
  {
660 6
    $this->connected = false;
661
662
    if (
663 6
        $this->doctrine_connection
664
        &&
665 6
        $this->doctrine_connection instanceof \Doctrine\DBAL\Connection
666
    ) {
667
668
      $connectedBefore = $this->doctrine_connection->isConnected();
669
670
      $this->doctrine_connection->close();
671
672
      $this->mysqli_link = null;
673
674
      if ($connectedBefore === true) {
675
        return !$this->doctrine_connection->isConnected();
676
      }
677
678
      return false;
679
    }
680
681
    if (
682 6
        $this->mysqli_link
683
        &&
684 6
        $this->mysqli_link instanceof \mysqli
685
    ) {
686 6
      $result = \mysqli_close($this->mysqli_link);
687 6
      $this->mysqli_link = null;
688
689 6
      return $result;
690
    }
691
692 3
    $this->mysqli_link = null;
693
694 3
    return false;
695
  }
696
697
  /**
698
   * Commits the current transaction and end the transaction.
699
   *
700
   * @return bool <p>bool true on success, false otherwise.</p>
701
   */
702 9 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...
703
  {
704 9
    if ($this->in_transaction === false) {
705
      $this->debug->displayError('Error: mysql server is not in transaction!', false);
706
707
      return false;
708
    }
709
710 9
    if ($this->mysqli_link) {
711 9
      $return = \mysqli_commit($this->mysqli_link);
712 9
      \mysqli_autocommit($this->mysqli_link, true);
713
    } elseif ($this->isDoctrinePDOConnection() === true) {
714
      $this->doctrine_connection->commit();
715
      $this->doctrine_connection->setAutoCommit(true);
716
717
      if ($this->doctrine_connection->isAutoCommit() === true) {
718
        $return = true;
719
      } else {
720
        $return = false;
721
      }
722
    }
723
724 9
    $this->in_transaction = false;
725
726 9
    return $return;
0 ignored issues
show
Bug introduced by
The variable $return does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
727
  }
728
729
  /**
730
   * Open a new connection to the MySQL server.
731
   *
732
   * @return bool
733
   *
734
   * @throws DBConnectException
735
   */
736 20
  public function connect(): bool
737
  {
738 20
    if ($this->isReady()) {
739 3
      return true;
740
    }
741
742 20
    if ($this->doctrine_connection) {
743
      $this->doctrine_connection->connect();
744
745
      $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
746
747 View Code Duplication
      if ($this->isDoctrineMySQLiConnection() === true) {
748
        /* @var $doctrineWrappedConnection \Doctrine\DBAL\Driver\Mysqli\MysqliConnection */
749
750
        $this->mysqli_link = $doctrineWrappedConnection->getWrappedResourceHandle();
751
752
        $this->connected = $this->doctrine_connection->isConnected();
753
754
        if (!$this->connected) {
755
          $error = 'Error connecting to mysql server: ' . $this->doctrine_connection->errorInfo();
756
          $this->debug->displayError($error, false);
757
          throw new DBConnectException($error, 101);
758
        }
759
760
        $this->set_charset($this->charset);
761
762
        return $this->isReady();
763
      }
764
765 View Code Duplication
      if ($this->isDoctrinePDOConnection() === true) {
766
767
        $this->mysqli_link = null;
768
769
        $this->connected = $this->doctrine_connection->isConnected();
770
771
        if (!$this->connected) {
772
          $error = 'Error connecting to mysql server: ' . $this->doctrine_connection->errorInfo();
773
          $this->debug->displayError($error, false);
774
          throw new DBConnectException($error, 101);
775
        }
776
777
        $this->set_charset($this->charset);
778
779
        return $this->isReady();
780
      }
781
    }
782
783 20
    $flags = null;
784
785 20
    \mysqli_report(MYSQLI_REPORT_STRICT);
786
    try {
787 20
      $this->mysqli_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 $mysqli_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...
788
789 20
      if (Helper::isMysqlndIsUsed() === true) {
790 20
        \mysqli_options($this->mysqli_link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
791
      }
792
793 20
      if ($this->ssl === true) {
794
795
        if (empty($this->clientcert)) {
796
          throw new DBConnectException('Error connecting to mysql server: clientcert not defined');
797
        }
798
799
        if (empty($this->clientkey)) {
800
          throw new DBConnectException('Error connecting to mysql server: clientkey not defined');
801
        }
802
803
        if (empty($this->cacert)) {
804
          throw new DBConnectException('Error connecting to mysql server: cacert not defined');
805
        }
806
807
        \mysqli_options($this->mysqli_link, MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
808
809
        /** @noinspection PhpParamsInspection */
810
        \mysqli_ssl_set(
811
            $this->mysqli_link,
812
            $this->clientkey,
813
            $this->clientcert,
814
            $this->cacert,
815
            null,
816
            null
817
        );
818
819
        $flags = MYSQLI_CLIENT_SSL;
820
      }
821
822
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
823 20
      $this->connected = @\mysqli_real_connect(
824 20
          $this->mysqli_link,
825 20
          $this->hostname,
826 20
          $this->username,
827 20
          $this->password,
828 20
          $this->database,
829 20
          $this->port,
830 20
          $this->socket,
831 20
          (int)$flags
832
      );
833
834 9
    } catch (\Exception $e) {
835 9
      $error = 'Error connecting to mysql server: ' . $e->getMessage();
836 9
      $this->debug->displayError($error, false);
837 9
      throw new DBConnectException($error, 100, $e);
838
    }
839 11
    \mysqli_report(MYSQLI_REPORT_OFF);
840
841 11
    $errno = \mysqli_connect_errno();
842 11
    if (!$this->connected || $errno) {
843
      $error = 'Error connecting to mysql server: ' . \mysqli_connect_error() . ' (' . $errno . ')';
844
      $this->debug->displayError($error, false);
845
      throw new DBConnectException($error, 101);
846
    }
847
848 11
    $this->set_charset($this->charset);
849
850 11
    return $this->isReady();
851
  }
852
853
  /**
854
   * Execute a "delete"-query.
855
   *
856
   * @param string       $table
857
   * @param string|array $where
858
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
859
   *
860
   * @return false|int <p>false on error</p>
861
   *
862
   * @throws QueryException
863
   */
864 4 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...
865
  {
866
    // init
867 4
    $table = \trim($table);
868
869 4
    if ($table === '') {
870 3
      $this->debug->displayError('Invalid table name, table name in empty.', false);
871
872 3
      return false;
873
    }
874
875 4
    if (\is_string($where)) {
876 3
      $WHERE = $this->escape($where, false);
877 4
    } elseif (\is_array($where)) {
878 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
879
    } else {
880 3
      $WHERE = '';
881
    }
882
883 4
    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...
884
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
885
    }
886
887 4
    $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE)";
888
889 4
    return $this->query($sql);
890
  }
891
892
  /**
893
   * Ends a transaction and commits if no errors, then ends autocommit.
894
   *
895
   * @return bool <p>This will return true or false indicating success of transactions.</p>
896
   */
897 12
  public function endTransaction(): bool
898
  {
899 12
    if ($this->in_transaction === false) {
900
      $this->debug->displayError('Error: mysql server is not in transaction!', false);
901
902
      return false;
903
    }
904
905 12
    if (!$this->errors()) {
906 3
      $return = $this->commit();
907
    } else {
908 9
      $this->rollback();
909 9
      $return = false;
910
    }
911
912 12
    if ($this->mysqli_link) {
913 12
      \mysqli_autocommit($this->mysqli_link, true);
914
    } elseif ($this->isDoctrinePDOConnection() === true) {
915
      $this->doctrine_connection->setAutoCommit(true);
916
917
      if ($this->doctrine_connection->isAutoCommit() === true) {
918
        $return = true;
919
      } else {
920
        $return = false;
921
      }
922
    }
923
924 12
    $this->in_transaction = false;
925
926 12
    return $return;
927
  }
928
929
  /**
930
   * Get all errors from "$this->errors".
931
   *
932
   * @return array|false <p>false === on errors</p>
933
   */
934 12
  public function errors()
935
  {
936 12
    $errors = $this->debug->getErrors();
937
938 12
    return \count($errors) > 0 ? $errors : false;
939
  }
940
941
  /**
942
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
943
   *
944
   * @param mixed     $var           bool: convert into "integer"<br />
945
   *                                 int: int (don't change it)<br />
946
   *                                 float: float (don't change it)<br />
947
   *                                 null: null (don't change it)<br />
948
   *                                 array: run escape() for every key => value<br />
949
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
950
   * @param bool      $stripe_non_utf8
951
   * @param bool      $html_entity_decode
952
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
953
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
954
   *                                 <strong>null</strong> => Convert the array into null, every time.
955
   *
956
   * @return mixed
957
   */
958 119
  public function escape($var = '', bool $stripe_non_utf8 = true, bool $html_entity_decode = false, $convert_array = false)
959
  {
960
    // [empty]
961 119
    if ($var === '') {
962 6
      return '';
963
    }
964
965
    // ''
966 119
    if ($var === "''") {
967
      return "''";
968
    }
969
970
    // check the type
971 119
    $type = \gettype($var);
972
973 119
    if ($type === 'object') {
974 9
      if ($var instanceof \DateTime) {
975 9
        $var = $var->format('Y-m-d H:i:s');
976 9
        $type = 'string';
977 6
      } elseif (\method_exists($var, '__toString')) {
978 6
        $var = (string)$var;
979 6
        $type = 'string';
980
      }
981
    }
982
983
    switch ($type) {
984 119
      case 'boolean':
985 9
        $var = (int)$var;
986 9
        break;
987
988 119
      case 'double':
989 119
      case 'integer':
990 70
        break;
991
992 113
      case 'string':
993 113
        if ($stripe_non_utf8 === true) {
994 23
          $var = UTF8::cleanup($var);
995
        }
996
997 113
        if ($html_entity_decode === true) {
998 3
          $var = UTF8::html_entity_decode($var);
999
        }
1000
1001 113
        $var = \get_magic_quotes_gpc() ? \stripslashes($var) : $var;
1002
1003
        if (
1004 113
            $this->mysqli_link
1005
            &&
1006 113
            $this->mysqli_link instanceof \mysqli
1007
        ) {
1008 113
          $var = \mysqli_real_escape_string($this->mysqli_link, $var);
1009
        } elseif ($this->isDoctrinePDOConnection() === true) {
1010
          $var = $this->getDoctrinePDOConnection()->quote($var);
1011
          $var = \substr($var, 1, -1);
1012
        }
1013
1014 113
        break;
1015
1016 9
      case 'array':
1017 6
        if ($convert_array === null) {
1018
1019 3
          if ($this->convert_null_to_empty_string === true) {
1020
            $var = "''";
1021
          } else {
1022 3
            $var = 'NULL';
1023
          }
1024
1025
        } else {
1026
1027 6
          $varCleaned = [];
1028 6
          foreach ((array)$var as $key => $value) {
1029
1030 6
            $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
1031 6
            $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
1032
1033
            /** @noinspection OffsetOperationsInspection */
1034 6
            $varCleaned[$key] = $value;
1035
          }
1036
1037 6 View Code Duplication
          if ($convert_array === true) {
1038 3
            $varCleaned = \implode(',', $varCleaned);
1039
1040 3
            $var = $varCleaned;
1041
          } else {
1042 6
            $var = $varCleaned;
1043
          }
1044
1045
        }
1046 6
        break;
1047
1048 9
      case 'NULL':
1049 6
        if ($this->convert_null_to_empty_string === true) {
1050
          $var = "''";
1051
        } else {
1052 6
          $var = 'NULL';
1053
        }
1054 6
        break;
1055
1056
      default:
1057 6
        throw new \InvalidArgumentException(sprintf('Not supported value "%s" of type %s.', print_r($var, true), $type));
1058
        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...
1059
    }
1060
1061 119
    return $var;
1062
  }
1063
1064
  /**
1065
   * Execute select/insert/update/delete sql-queries.
1066
   *
1067
   * @param string  $query    <p>sql-query</p>
1068
   * @param bool    $useCache optional <p>use cache?</p>
1069
   * @param int     $cacheTTL optional <p>cache-ttl in seconds</p>
1070
   * @param DB|null $db       optional <p>the database connection</p>
1071
   *
1072
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
1073
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
1074
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1075
   *               "true" by e.g. "DROP"-queries<br />
1076
   *               "false" on error
1077
   *
1078
   * @throws QueryException
1079
   */
1080 9
  public static function execSQL(string $query, bool $useCache = false, int $cacheTTL = 3600, self $db = null)
1081
  {
1082
    // init
1083 9
    $cacheKey = null;
1084 9
    if (!$db) {
1085 9
      $db = self::getInstance();
1086
    }
1087
1088 9 View Code Duplication
    if ($useCache === true) {
1089 3
      $cache = new Cache(null, null, false, $useCache);
1090 3
      $cacheKey = 'sql-' . \md5($query);
1091
1092
      if (
1093 3
          $cache->getCacheIsReady() === true
1094
          &&
1095 3
          $cache->existsItem($cacheKey)
1096
      ) {
1097 3
        return $cache->getItem($cacheKey);
1098
      }
1099
1100
    } else {
1101 9
      $cache = false;
1102
    }
1103
1104 9
    $result = $db->query($query);
1105
1106 9
    if ($result instanceof Result) {
1107
1108 3
      $return = $result->fetchAllArray();
1109
1110
      // save into the cache
1111 View Code Duplication
      if (
1112 3
          $cacheKey !== null
1113
          &&
1114 3
          $useCache === true
1115
          &&
1116 3
          $cache instanceof Cache
1117
          &&
1118 3
          $cache->getCacheIsReady() === true
1119
      ) {
1120 3
        $cache->setItem($cacheKey, $return, $cacheTTL);
1121
      }
1122
1123
    } else {
1124 6
      $return = $result;
1125
    }
1126
1127 9
    return $return;
1128
  }
1129
1130
  /**
1131
   * Get all table-names via "SHOW TABLES".
1132
   *
1133
   * @return array
1134
   */
1135 3
  public function getAllTables(): array
1136
  {
1137 3
    $query = 'SHOW TABLES';
1138 3
    $result = $this->query($query);
1139
1140 3
    return $result->fetchAllArray();
1141
  }
1142
1143
  /**
1144
   * @return array
1145
   */
1146 8
  public function getConfig()
1147
  {
1148
    $config = [
1149 8
        'hostname'   => $this->hostname,
1150 8
        'username'   => $this->username,
1151 8
        'password'   => $this->password,
1152 8
        'port'       => $this->port,
1153 8
        'database'   => $this->database,
1154 8
        'socket'     => $this->socket,
1155 8
        'charset'    => $this->charset,
1156 8
        'cacert'     => $this->cacert,
1157 8
        'clientcert' => $this->clientcert,
1158 8
        'clientkey'  => $this->clientkey,
1159
    ];
1160
1161 8
    if ($this->doctrine_connection instanceof \Doctrine\DBAL\Connection) {
1162
      $config += $this->doctrine_connection->getParams();
1163
    }
1164
1165 8
    return $config;
1166
  }
1167
1168
  /**
1169
   * @return Debug
1170
   */
1171 9
  public function getDebugger(): Debug
1172
  {
1173 9
    return $this->debug;
1174
  }
1175
1176
  /**
1177
   * @return null|\Doctrine\DBAL\Connection|null
1178
   */
1179 2
  public function getDoctrineConnection()
1180
  {
1181 2
    return $this->doctrine_connection;
1182
  }
1183
1184
  /**
1185
   * @return false|\Doctrine\DBAL\Driver\Connection
1186
   */
1187 View Code Duplication
  private function getDoctrinePDOConnection()
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...
1188
  {
1189
    if ($this->doctrine_connection) {
1190
      $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1191
      if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
1192
        return $doctrineWrappedConnection;
1193
      }
1194
    }
1195
1196
    return false;
1197
  }
1198
1199
  /**
1200
   * Get errors from "$this->errors".
1201
   *
1202
   * @return array
1203
   */
1204 3
  public function getErrors(): array
1205
  {
1206 3
    return $this->debug->getErrors();
1207
  }
1208
1209
  /**
1210
   * @param string $hostname             <p>Hostname of the mysql server</p>
1211
   * @param string $username             <p>Username for the mysql connection</p>
1212
   * @param string $password             <p>Password for the mysql connection</p>
1213
   * @param string $database             <p>Database for the mysql connection</p>
1214
   * @param int    $port                 <p>default is (int)3306</p>
1215
   * @param string $charset              <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1216
   * @param bool   $exit_on_error        <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
1217
   *                                     Use false to disable it.</p>
1218
   * @param bool   $echo_on_error        <p>Echo the error if "checkForDev()" returns true.
1219
   *                                     Use false to disable it.</p>
1220
   * @param string $logger_class_name
1221
   * @param string $logger_level         <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1222
   * @param array  $extra_config         <p>
1223
   *                                     're_connect'    => bool<br>
1224
   *                                     'session_to_db' => bool<br>
1225
   *                                     'doctrine'      => \Doctrine\DBAL\Connection<br>
1226
   *                                     'socket'        => 'string (path)'<br>
1227
   *                                     'ssl'           => bool<br>
1228
   *                                     'clientkey'     => 'string (path)'<br>
1229
   *                                     'clientcert'    => 'string (path)'<br>
1230
   *                                     'cacert'        => 'string (path)'<br>
1231
   *                                     </p>
1232
   *
1233
   * @return self
1234
   */
1235 231
  public static function getInstance(
1236
      string $hostname = '',
1237
      string $username = '',
1238
      string $password = '',
1239
      string $database = '',
1240
      $port = 3306,
1241
      string $charset = 'utf8',
1242
      bool $exit_on_error = true,
1243
      bool $echo_on_error = true,
1244
      string $logger_class_name = '',
1245
      string $logger_level = '',
1246
      array $extra_config = []
1247
  ): self
1248
  {
1249
    /**
1250
     * @var $instance self[]
1251
     */
1252 231
    static $instance = [];
1253
1254
    /**
1255
     * @var $firstInstance self
1256
     */
1257 231
    static $firstInstance = null;
1258
1259
    // fallback
1260 231
    if (!$charset) {
1261 121
      $charset = 'utf8';
1262
    }
1263
1264
    if (
1265 231
        '' . $hostname . $username . $password . $database . $port . $charset == '' . $port . $charset
1266
        &&
1267 231
        null !== $firstInstance
1268
    ) {
1269 143
      if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1270
        $firstInstance->reconnect(true);
1271
      }
1272
1273 143
      return $firstInstance;
1274
    }
1275
1276 150
    $extra_config_string = '';
1277 150
    foreach ($extra_config as $extra_config_key => $extra_config_value) {
1278
      if (\is_object($extra_config_value)) {
1279
        $extra_config_value_tmp = \spl_object_hash($extra_config_value);
1280
      } else {
1281
        $extra_config_value_tmp = (string)$extra_config_value;
1282
      }
1283
      $extra_config_string .= $extra_config_key . $extra_config_value_tmp;
1284
    }
1285
1286 150
    $connection = \md5(
1287 150
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . $extra_config_string
1288
    );
1289
1290 150
    if (!isset($instance[$connection])) {
1291 23
      $instance[$connection] = new self(
1292 23
          $hostname,
1293 23
          $username,
1294 23
          $password,
1295 23
          $database,
1296 23
          $port,
1297 23
          $charset,
1298 23
          $exit_on_error,
1299 23
          $echo_on_error,
1300 23
          $logger_class_name,
1301 23
          $logger_level,
1302 23
          $extra_config
1303
      );
1304
1305 5
      if (null === $firstInstance) {
1306 1
        $firstInstance = $instance[$connection];
1307
      }
1308
    }
1309
1310 138
    if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1311
      $instance[$connection]->reconnect(true);
1312
    }
1313
1314 138
    return $instance[$connection];
1315
  }
1316
1317
  /**
1318
   * @param \Doctrine\DBAL\Connection $doctrine
1319
   * @param string                    $charset       <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1320
   * @param bool                      $exit_on_error <p>Throw a 'Exception' when a query failed, otherwise it will
1321
   *                                                 return 'false'. Use false to disable it.</p>
1322
   * @param bool                      $echo_on_error <p>Echo the error if "checkForDev()" returns true.
1323
   *                                                 Use false to disable it.</p>
1324
   * @param string                    $logger_class_name
1325
   * @param string                    $logger_level  <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1326
   * @param array                     $extra_config  <p>
1327
   *                                                 're_connect'    => bool<br>
1328
   *                                                 'session_to_db' => bool<br>
1329
   *                                                 'doctrine'      => \Doctrine\DBAL\Connection<br>
1330
   *                                                 'socket'        => 'string (path)'<br>
1331
   *                                                 'ssl'           => bool<br>
1332
   *                                                 'clientkey'     => 'string (path)'<br>
1333
   *                                                 'clientcert'    => 'string (path)'<br>
1334
   *                                                 'cacert'        => 'string (path)'<br>
1335
   *                                                 </p>
1336
   *
1337
   * @return self
1338
   */
1339 55
  public static function getInstanceDoctrineHelper(
1340
      \Doctrine\DBAL\Connection $doctrine,
1341
      string $charset = 'utf8',
1342
      bool $exit_on_error = true,
1343
      bool $echo_on_error = true,
1344
      string $logger_class_name = '',
1345
      string $logger_level = '',
1346
      array $extra_config = []
1347
  ): self
1348
  {
1349 55
    $extra_config['doctrine'] = $doctrine;
1350
1351 55
    return self::getInstance(
1352 55
        '',
1353 55
        '',
1354 55
        '',
1355 55
        '',
1356 55
        3306,
1357 55
        $charset,
1358 55
        $exit_on_error,
1359 55
        $echo_on_error,
1360 55
        $logger_class_name,
1361 55
        $logger_level,
1362 55
        $extra_config
1363
    );
1364
  }
1365
1366
  /**
1367
   * Get the mysqli-link (link identifier returned by mysqli-connect).
1368
   *
1369
   * @return null|\mysqli
1370
   */
1371 53
  public function getLink()
1372
  {
1373 53
    return $this->mysqli_link;
1374
  }
1375
1376
  /**
1377
   * Get the current charset.
1378
   *
1379
   * @return string
1380
   */
1381 3
  public function get_charset(): string
1382
  {
1383 3
    return $this->charset;
1384
  }
1385
1386
  /**
1387
   * Check if we are in a transaction.
1388
   *
1389
   * @return bool
1390
   */
1391
  public function inTransaction(): bool
1392
  {
1393
    return $this->in_transaction;
1394
  }
1395
1396
  /**
1397
   * Execute a "insert"-query.
1398
   *
1399
   * @param string      $table
1400
   * @param array       $data
1401
   * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1402
   *
1403
   * @return false|int <p>false on error</p>
1404
   *
1405
   * @throws QueryException
1406
   */
1407 74
  public function insert(string $table, array $data = [], string $databaseName = null)
1408
  {
1409
    // init
1410 74
    $table = \trim($table);
1411
1412 74
    if ($table === '') {
1413 6
      $this->debug->displayError('Invalid table name, table name in empty.', false);
1414
1415 6
      return false;
1416
    }
1417
1418 71
    if (\count($data) === 0) {
1419 9
      $this->debug->displayError('Invalid data for INSERT, data is empty.', false);
1420
1421 9
      return false;
1422
    }
1423
1424 65
    $SET = $this->_parseArrayPair($data);
1425
1426 65
    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...
1427
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
1428
    }
1429
1430 65
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET";
1431
1432 65
    return $this->query($sql);
1433
  }
1434
1435
  /**
1436
   * Returns the auto generated id used in the last query.
1437
   *
1438
   * @return int|string
1439
   */
1440 104
  public function insert_id()
1441
  {
1442 104
    if ($this->mysqli_link) {
1443 104
      return \mysqli_insert_id($this->mysqli_link);
1444
    }
1445
1446
    if ($this->getDoctrinePDOConnection()) {
1447
      return $this->getDoctrinePDOConnection()->lastInsertId();
1448
    }
1449
  }
1450
1451
  /**
1452
   * @return bool
1453
   */
1454
  public function isDoctrineMySQLiConnection(): bool
1455
  {
1456
    if ($this->doctrine_connection) {
1457
      $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1458
      if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliConnection) {
1459
        return true;
1460
      }
1461
    }
1462
1463
    return false;
1464
  }
1465
1466
  /**
1467
   * @return bool
1468
   */
1469 9 View Code Duplication
  public function isDoctrinePDOConnection(): 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...
1470
  {
1471 9
    if ($this->doctrine_connection) {
1472
      $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1473
      if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
1474
        return true;
1475
      }
1476
    }
1477
1478 9
    return false;
1479
  }
1480
1481
  /**
1482
   * Check if db-connection is ready.
1483
   *
1484
   * @return bool
1485
   */
1486 184
  public function isReady(): bool
1487
  {
1488 184
    return $this->connected ? true : false;
1489
  }
1490
1491
  /**
1492
   * Get the last sql-error.
1493
   *
1494
   * @return string|false <p>false === there was no error</p>
1495
   */
1496 3
  public function lastError()
1497
  {
1498 3
    $errors = $this->debug->getErrors();
1499
1500 3
    return \count($errors) > 0 ? end($errors) : false;
1501
  }
1502
1503
  /**
1504
   * Execute a sql-multi-query.
1505
   *
1506
   * @param string $sql
1507
   *
1508
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1509
   *                        "bool" by only "<b>INSERT</b>"-queries<br />
1510
   *                        "bool" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1511
   *                        "bool" by only by e.g. "DROP"-queries<br />
1512
   *
1513
   * @throws QueryException
1514
   */
1515 3
  public function multi_query(string $sql)
1516
  {
1517 3
    if (!$this->isReady()) {
1518
      return false;
1519
    }
1520
1521 3 View Code Duplication
    if (!$sql || $sql === '') {
1522 3
      $this->debug->displayError('Can not execute an empty query.', false);
1523
1524 3
      return false;
1525
    }
1526
1527 3
    if ($this->isDoctrinePDOConnection() === true) {
1528
1529
      $query_start_time = microtime(true);
1530
      $queryException = null;
1531
      $query_result_doctrine = false;
1532
1533
      try {
1534
        $query_result_doctrine = $this->doctrine_connection->prepare($sql);
1535
        $resultTmp = $query_result_doctrine->execute();
1536
        $mysqli_field_count = $query_result_doctrine->columnCount();
1537
      } catch (\Exception $e) {
1538
        $resultTmp = false;
1539
        $mysqli_field_count = null;
1540
1541
        $queryException = $e;
1542
      }
1543
1544
      $query_duration = microtime(true) - $query_start_time;
1545
1546
      $this->debug->logQuery($sql, $query_duration, 0);
1547
1548
      $returnTheResult = false;
1549
      $result = [];
1550
1551
      if ($resultTmp) {
1552
1553
        if ($mysqli_field_count) {
1554
1555
          if (
1556
              $query_result_doctrine
1557
              &&
1558
              $query_result_doctrine instanceof \Doctrine\DBAL\Statement
1559
          ) {
1560
            $result = $query_result_doctrine;
1561
          }
1562
1563
        } else {
1564
          $result = $resultTmp;
1565
        }
1566
1567
        if (
1568
            $result instanceof \Doctrine\DBAL\Statement
1569
            &&
1570
            $result->columnCount() > 0
1571
        ) {
1572
          $returnTheResult = true;
1573
1574
          // return query result object
1575
          $result = [new Result($sql, $result)];
1576
        } else {
1577
          $result = [$result];
1578
        }
1579
1580
      } else {
1581
1582
        // log the error query
1583
        $this->debug->logQuery($sql, $query_duration, 0, true);
1584
1585
        if (
1586
            isset($queryException)
1587
            &&
1588
            $queryException instanceof \Doctrine\DBAL\Query\QueryException
1589
        ) {
1590
          return $this->queryErrorHandling($queryException->getMessage(), $queryException->getCode(), $sql, false, true);
1591
        }
1592
      }
1593
1594
    } else {
1595
1596 3
      $query_start_time = microtime(true);
1597 3
      $resultTmp = \mysqli_multi_query($this->mysqli_link, $sql);
1598 3
      $query_duration = microtime(true) - $query_start_time;
1599
1600 3
      $this->debug->logQuery($sql, $query_duration, 0);
1601
1602 3
      $returnTheResult = false;
1603 3
      $result = [];
1604
1605 3
      if ($resultTmp) {
1606
        do {
1607
1608 3
          $resultTmpInner = \mysqli_store_result($this->mysqli_link);
1609
1610 3
          if ($resultTmpInner instanceof \mysqli_result) {
1611
1612 3
            $returnTheResult = true;
1613 3
            $result[] = new Result($sql, $resultTmpInner);
1614
1615
          } elseif (
1616 3
              $resultTmpInner === true
1617
              ||
1618 3
              !\mysqli_errno($this->mysqli_link)
1619
          ) {
1620
1621 3
            $result[] = true;
1622
1623
          } else {
1624
1625
            $result[] = false;
1626
1627
          }
1628
1629 3
        } while (\mysqli_more_results($this->mysqli_link) === true ? \mysqli_next_result($this->mysqli_link) : false);
1630
1631
      } else {
1632
1633
        // log the error query
1634 3
        $this->debug->logQuery($sql, $query_duration, 0, true);
1635
1636 3
        return $this->queryErrorHandling(\mysqli_error($this->mysqli_link), \mysqli_errno($this->mysqli_link), $sql, false, true);
1637
      }
1638
1639
    }
1640
1641
    // return the result only if there was a "SELECT"-query
1642 3
    if ($returnTheResult === true) {
1643 3
      return $result;
1644
    }
1645
1646
    if (
1647 3
        \count($result) > 0
1648
        &&
1649 3
        \in_array(false, $result, true) === false
1650
    ) {
1651 3
      return true;
1652
    }
1653
1654
    return false;
1655
  }
1656
1657
  /**
1658
   * Count number of rows found matching a specific query.
1659
   *
1660
   * @param string $query
1661
   *
1662
   * @return int
1663
   */
1664 3
  public function num_rows(string $query): int
1665
  {
1666 3
    $check = $this->query($query);
1667
1668
    if (
1669 3
        $check === false
1670
        ||
1671 3
        !$check instanceof Result
1672
    ) {
1673
      return 0;
1674
    }
1675
1676 3
    return $check->num_rows;
1677
  }
1678
1679
  /**
1680
   * Pings a server connection, or tries to reconnect
1681
   * if the connection has gone down.
1682
   *
1683
   * @return bool
1684
   */
1685 9
  public function ping(): bool
1686
  {
1687 9
    if ($this->connected === false) {
1688 3
      return false;
1689
    }
1690
1691 6
    if ($this->isDoctrinePDOConnection() === true) {
1692
      return $this->doctrine_connection->ping();
1693
    }
1694
1695
    if (
1696 6
        $this->mysqli_link
1697
        &&
1698 6
        $this->mysqli_link instanceof \mysqli
1699
    ) {
1700 6
      return \mysqli_ping($this->mysqli_link);
1701
    }
1702
1703
    return false;
1704
  }
1705
1706
  /**
1707
   * Get a new "Prepare"-Object for your sql-query.
1708
   *
1709
   * @param string $query
1710
   *
1711
   * @return Prepare
1712
   */
1713 2
  public function prepare(string $query): Prepare
1714
  {
1715 2
    return new Prepare($this, $query);
1716
  }
1717
1718
  /**
1719
   * Execute a sql-query and return the result-array for select-statements.
1720
   *
1721
   * @param string $query
1722
   *
1723
   * @return mixed
1724
   * @deprecated
1725
   * @throws \Exception
1726
   */
1727 3
  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...
1728
  {
1729 3
    $db = self::getInstance();
1730
1731 3
    $args = \func_get_args();
1732
    /** @noinspection SuspiciousAssignmentsInspection */
1733 3
    $query = \array_shift($args);
1734 3
    $query = \str_replace('?', '%s', $query);
1735 3
    $args = \array_map(
1736
        [
1737 3
            $db,
1738 3
            'escape',
1739
        ],
1740 3
        $args
1741
    );
1742 3
    \array_unshift($args, $query);
1743 3
    $query = \sprintf(...$args);
1744 3
    $result = $db->query($query);
1745
1746 3
    if ($result instanceof Result) {
1747 3
      return $result->fetchAllArray();
1748
    }
1749
1750 3
    return $result;
1751
  }
1752
1753
  /**
1754
   * Execute a sql-query.
1755
   *
1756
   * example:
1757
   * <code>
1758
   * $sql = "INSERT INTO TABLE_NAME_HERE
1759
   *   SET
1760
   *     foo = :foo,
1761
   *     bar = :bar
1762
   * ";
1763
   * $insert_id = $db->query(
1764
   *   $sql,
1765
   *   [
1766
   *     'foo' => 1.1,
1767
   *     'bar' => 1,
1768
   *   ]
1769
   * );
1770
   * </code>
1771
   *
1772
   * @param string     $sql               <p>The sql query-string.</p>
1773
   *
1774
   * @param array|bool $params            <p>
1775
   *                                      "array" of sql-query-parameters<br/>
1776
   *                                      "false" if you don't need any parameter (default)<br/>
1777
   *                                      </p>
1778
   *
1779
   * @return bool|int|Result              <p>
1780
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
1781
   *                                      "int|string" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
1782
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1783
   *                                      "true" by e.g. "DROP"-queries<br />
1784
   *                                      "false" on error
1785
   *                                      </p>
1786
   *
1787
   * @throws QueryException
1788
   */
1789 164
  public function query(string $sql = '', $params = false)
1790
  {
1791 164
    if (!$this->isReady()) {
1792
      return false;
1793
    }
1794
1795 164 View Code Duplication
    if (!$sql || $sql === '') {
1796 12
      $this->debug->displayError('Can not execute an empty query.', false);
1797
1798 12
      return false;
1799
    }
1800
1801
    if (
1802 158
        $params !== false
1803
        &&
1804 158
        \is_array($params)
1805
        &&
1806 158
        \count($params) > 0
1807
    ) {
1808 17
      $parseQueryParams = $this->_parseQueryParams($sql, $params);
1809 17
      $parseQueryParamsByName = $this->_parseQueryParamsByName($parseQueryParams['sql'], $parseQueryParams['params']);
1810 17
      $sql = $parseQueryParamsByName['sql'];
1811
    }
1812
1813
    // DEBUG
1814
    // var_dump($params);
1815
    // echo $sql . "\n";
1816
1817 158
    $query_start_time = \microtime(true);
1818 158
    $queryException = null;
1819 158
    $query_result_doctrine = false;
1820
1821 158
    if ($this->doctrine_connection) {
1822
1823
      try {
1824
        $query_result_doctrine = $this->doctrine_connection->prepare($sql);
1825
        $query_result = $query_result_doctrine->execute();
1826
        $mysqli_field_count = $query_result_doctrine->columnCount();
1827
      } catch (\Exception $e) {
1828
        $query_result = false;
1829
        $mysqli_field_count = null;
1830
1831
        $queryException = $e;
1832
      }
1833
1834
    } else {
1835
1836 158
      $query_result = \mysqli_real_query($this->mysqli_link, $sql);
1837 158
      $mysqli_field_count = \mysqli_field_count($this->mysqli_link);
1838
1839
    }
1840
1841 158
    $query_duration = \microtime(true) - $query_start_time;
1842
1843 158
    $this->query_count++;
1844
1845 158
    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...
1846
1847 116
      if ($this->doctrine_connection) {
1848
1849
        $result = false;
1850
        if (
1851
            $query_result_doctrine
1852
            &&
1853
            $query_result_doctrine instanceof \Doctrine\DBAL\Statement
1854
        ) {
1855
          $result = $query_result_doctrine;
1856
        }
1857
1858
      } else {
1859
1860 116
        $result = \mysqli_store_result($this->mysqli_link);
1861
1862
      }
1863
    } else {
1864 114
      $result = $query_result;
1865
    }
1866
1867
    if (
1868 158
        $result instanceof \Doctrine\DBAL\Statement
1869
        &&
1870 158
        $result->columnCount() > 0
1871
    ) {
1872
1873
      // log the select query
1874
      $this->debug->logQuery($sql, $query_duration, $mysqli_field_count);
1875
1876
      // return query result object
1877
      return new Result($sql, $result);
1878
    }
1879
1880 158
    if ($result instanceof \mysqli_result) {
1881
1882
      // log the select query
1883 113
      $this->debug->logQuery($sql, $query_duration, $mysqli_field_count);
1884
1885
      // return query result object
1886 113
      return new Result($sql, $result);
1887
    }
1888
1889 120
    if ($query_result === true) {
1890
1891
      // "INSERT" || "REPLACE"
1892 111
      if (\preg_match('/^\s*?(?:INSERT|REPLACE)\s+/i', $sql)) {
1893
1894 104
        $insert_id = $this->insert_id();
1895
1896 104
        $this->debug->logQuery($sql, $query_duration, $insert_id);
1897
1898 104
        return $insert_id;
1899
      }
1900
1901
      // "UPDATE" || "DELETE"
1902 54
      if (\preg_match('/^\s*?(?:UPDATE|DELETE)\s+/i', $sql)) {
1903
1904 28
        if ($this->mysqli_link) {
1905 28
          $this->affected_rows = $this->affected_rows();
1906
        } elseif ($query_result_doctrine) {
1907
          $this->affected_rows = $query_result_doctrine->rowCount();
1908
        }
1909
1910 28
        $this->debug->logQuery($sql, $query_duration, $this->affected_rows);
1911
1912 28
        return $this->affected_rows;
1913
      }
1914
1915
      // log the ? query
1916 27
      $this->debug->logQuery($sql, $query_duration, 0);
1917
1918 27
      return true;
1919
    }
1920
1921
    // log the error query
1922 33
    $this->debug->logQuery($sql, $query_duration, 0, true);
1923
1924 33
    if ($queryException) {
1925
      return $this->queryErrorHandling($queryException->getMessage(), $queryException->getCode(), $sql, $params);
1926
    }
1927
1928 33
    if ($this->mysqli_link) {
1929 33
      return $this->queryErrorHandling(\mysqli_error($this->mysqli_link), \mysqli_errno($this->mysqli_link), $sql, $params);
1930
    }
1931
1932
    return false;
1933
  }
1934
1935
  /**
1936
   * Error-handling for the sql-query.
1937
   *
1938
   * @param string     $errorMessage
1939
   * @param int        $errorNumber
1940
   * @param string     $sql
1941
   * @param array|bool $sqlParams <p>false if there wasn't any parameter</p>
1942
   * @param bool       $sqlMultiQuery
1943
   *
1944
   * @return mixed|false
1945
   *
1946
   * @throws QueryException
1947
   * @throws DBGoneAwayException
1948
   */
1949 39
  private function queryErrorHandling(string $errorMessage, int $errorNumber, string $sql, $sqlParams = false, bool $sqlMultiQuery = false)
1950
  {
1951
    if (
1952 39
        $errorMessage === 'DB server has gone away'
1953
        ||
1954 36
        $errorMessage === 'MySQL server has gone away'
1955
        ||
1956 39
        $errorNumber === 2006
1957
    ) {
1958 3
      static $RECONNECT_COUNTER;
1959
1960
      // exit if we have more then 3 "DB server has gone away"-errors
1961 3
      if ($RECONNECT_COUNTER > 3) {
1962
        $this->debug->mailToAdmin('DB-Fatal-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql, 5);
1963
        throw new DBGoneAwayException($errorMessage);
1964
      }
1965
1966 3
      $this->debug->mailToAdmin('DB-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1967
1968
      // reconnect
1969 3
      $RECONNECT_COUNTER++;
1970 3
      $this->reconnect(true);
1971
1972
      // re-run the current (non multi) query
1973 3
      if ($sqlMultiQuery === false) {
1974 3
        return $this->query($sql, $sqlParams);
1975
      }
1976
1977
      return false;
1978
    }
1979
1980 36
    $this->debug->mailToAdmin('SQL-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1981
1982 36
    $force_exception_after_error = null; // auto
1983 36
    if ($this->in_transaction === true) {
1984 12
      $force_exception_after_error = false;
1985
    }
1986
    // this query returned an error, we must display it (only for dev) !!!
1987
1988 36
    $this->debug->displayError($errorMessage . '(' . $errorNumber . ') ' . ' | ' . $sql, $force_exception_after_error);
1989
1990 36
    return false;
1991
  }
1992
1993
  /**
1994
   * Quote && Escape e.g. a table name string.
1995
   *
1996
   * @param mixed $str
1997
   *
1998
   * @return string
1999
   */
2000 86
  public function quote_string($str): string
2001
  {
2002 86
    $str = \str_replace(
2003 86
        '`',
2004 86
        '``',
2005 86
        \trim(
2006 86
            (string)$this->escape($str, false),
2007 86
            '`'
2008
        )
2009
    );
2010
2011 86
    return '`' . $str . '`';
2012
  }
2013
2014
  /**
2015
   * Reconnect to the MySQL-Server.
2016
   *
2017
   * @param bool $checkViaPing
2018
   *
2019
   * @return bool
2020
   */
2021 7
  public function reconnect(bool $checkViaPing = false): bool
2022
  {
2023 7
    $ping = false;
2024 7
    if ($checkViaPing === true) {
2025 6
      $ping = $this->ping();
2026
    }
2027
2028 7
    if ($ping === false) {
2029 7
      $this->connected = false;
2030 7
      $this->connect();
2031
    }
2032
2033 7
    return $this->isReady();
2034
  }
2035
2036
  /**
2037
   * Execute a "replace"-query.
2038
   *
2039
   * @param string      $table
2040
   * @param array       $data
2041
   * @param null|string $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2042
   *
2043
   * @return false|int <p>false on error</p>
2044
   *
2045
   * @throws QueryException
2046
   */
2047 3
  public function replace(string $table, array $data = [], string $databaseName = null)
2048
  {
2049
    // init
2050 3
    $table = \trim($table);
2051
2052 3
    if ($table === '') {
2053 3
      $this->debug->displayError('Invalid table name, table name in empty.', false);
2054
2055 3
      return false;
2056
    }
2057
2058 3
    if (\count($data) === 0) {
2059 3
      $this->debug->displayError('Invalid data for REPLACE, data is empty.', false);
2060
2061 3
      return false;
2062
    }
2063
2064
    // extracting column names
2065 3
    $columns = \array_keys($data);
2066 3
    foreach ($columns as $k => $_key) {
2067
      /** @noinspection AlterInForeachInspection */
2068 3
      $columns[$k] = $this->quote_string($_key);
2069
    }
2070
2071 3
    $columns = \implode(',', $columns);
2072
2073
    // extracting values
2074 3
    foreach ($data as $k => $_value) {
2075
      /** @noinspection AlterInForeachInspection */
2076 3
      $data[$k] = $this->secure($_value);
2077
    }
2078 3
    $values = \implode(',', $data);
2079
2080 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...
2081
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2082
    }
2083
2084 3
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values)";
2085
2086 3
    return $this->query($sql);
2087
  }
2088
2089
  /**
2090
   * Rollback in a transaction and end the transaction.
2091
   *
2092
   * @return bool <p>bool true on success, false otherwise.</p>
2093
   */
2094 12 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...
2095
  {
2096 12
    if ($this->in_transaction === false) {
2097
      $this->debug->displayError('Error: mysql server is not in transaction!', false);
2098
2099
      return false;
2100
    }
2101
2102 12
    if ($this->mysqli_link) {
2103 12
      $return = \mysqli_rollback($this->mysqli_link);
2104 12
      \mysqli_autocommit($this->mysqli_link, true);
2105
    } elseif ($this->isDoctrinePDOConnection() === true) {
2106
      $this->doctrine_connection->rollBack();
2107
      $this->doctrine_connection->setAutoCommit(true);
2108
2109
      if ($this->doctrine_connection->isAutoCommit() === true) {
2110
        $return = true;
2111
      } else {
2112
        $return = false;
2113
      }
2114
    }
2115
2116 12
    $this->in_transaction = false;
2117
2118 12
    return $return;
0 ignored issues
show
Bug introduced by
The variable $return does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2119
  }
2120
2121
  /**
2122
   * Try to secure a variable, so can you use it in sql-queries.
2123
   *
2124
   * <p>
2125
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
2126
   * 1. parse into "int"
2127
   * </p><br />
2128
   *
2129
   * <p>
2130
   * <strong>float:</strong><br />
2131
   * 1. return "float"
2132
   * </p><br />
2133
   *
2134
   * <p>
2135
   * <strong>string:</strong><br />
2136
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
2137
   * 2. trim '<br />
2138
   * 3. escape the string (and remove non utf-8 chars)<br />
2139
   * 4. trim ' again (because we maybe removed some chars)<br />
2140
   * 5. add ' around the new string<br />
2141
   * </p><br />
2142
   *
2143
   * <p>
2144
   * <strong>array:</strong><br />
2145
   * 1. return null
2146
   * </p><br />
2147
   *
2148
   * <p>
2149
   * <strong>object:</strong><br />
2150
   * 1. return false
2151
   * </p><br />
2152
   *
2153
   * <p>
2154
   * <strong>null:</strong><br />
2155
   * 1. return null
2156
   * </p>
2157
   *
2158
   * @param mixed     $var
2159
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
2160
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
2161
   *                                 <strong>null</strong> => Convert the array into null, every time.
2162
   *
2163
   * @return mixed
2164
   */
2165 97
  public function secure($var, $convert_array = true)
2166
  {
2167 97
    if (\is_array($var)) {
2168 6
      if ($convert_array === null) {
2169
2170
        if ($this->convert_null_to_empty_string === true) {
2171
          $var = "''";
2172
        } else {
2173
          $var = 'NULL';
2174
        }
2175
2176
      } else {
2177
2178 6
        $varCleaned = [];
2179 6
        foreach ((array)$var as $key => $value) {
2180
2181 6
          $key = $this->escape($key, false, false, $convert_array);
2182 6
          $value = $this->secure($value);
2183
2184
          /** @noinspection OffsetOperationsInspection */
2185 6
          $varCleaned[$key] = $value;
2186
        }
2187
2188 6 View Code Duplication
        if ($convert_array === true) {
2189 6
          $varCleaned = \implode(',', $varCleaned);
2190
2191 6
          $var = $varCleaned;
2192
        } else {
2193
          $var = $varCleaned;
2194
        }
2195
2196
      }
2197
2198 6
      return $var;
2199
    }
2200
2201 97
    if ($var === '') {
2202 6
      return "''";
2203
    }
2204
2205 97
    if ($var === "''") {
2206 3
      return "''";
2207
    }
2208
2209 97
    if ($var === null) {
2210 4
      if ($this->convert_null_to_empty_string === true) {
2211 3
        return "''";
2212
      }
2213
2214 4
      return 'NULL';
2215
    }
2216
2217 96
    if (\in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
2218 3
      return $var;
2219
    }
2220
2221 96
    if (\is_string($var)) {
2222 83
      $var = \trim($var, "'");
2223
    }
2224
2225 96
    $var = $this->escape($var, false, false, null);
2226
2227 93
    if (\is_string($var)) {
2228 83
      $var = "'" . \trim($var, "'") . "'";
2229
    }
2230
2231 93
    return $var;
2232
  }
2233
2234
  /**
2235
   * Execute a "select"-query.
2236
   *
2237
   * @param string       $table
2238
   * @param string|array $where
2239
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2240
   *
2241
   * @return false|Result <p>false on error</p>
2242
   *
2243
   * @throws QueryException
2244
   */
2245 62 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...
2246
  {
2247
    // init
2248 62
    $table = \trim($table);
2249
2250 62
    if ($table === '') {
2251 3
      $this->debug->displayError('Invalid table name, table name in empty.', false);
2252
2253 3
      return false;
2254
    }
2255
2256 62
    if (\is_string($where)) {
2257 24
      $WHERE = $this->escape($where, false);
2258 41
    } elseif (\is_array($where)) {
2259 41
      $WHERE = $this->_parseArrayPair($where, 'AND');
2260
    } else {
2261 3
      $WHERE = '';
2262
    }
2263
2264 62
    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...
2265
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2266
    }
2267
2268 62
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE)";
2269
2270 62
    return $this->query($sql);
2271
  }
2272
2273
  /**
2274
   * Selects a different database than the one specified on construction.
2275
   *
2276
   * @param string $database <p>Database name to switch to.</p>
2277
   *
2278
   * @return bool <p>bool true on success, false otherwise.</p>
2279
   */
2280
  public function select_db(string $database): bool
2281
  {
2282
    if (!$this->isReady()) {
2283
      return false;
2284
    }
2285
2286
    if ($this->mysqli_link) {
2287
      return \mysqli_select_db($this->mysqli_link, $database);
2288
    }
2289
2290
    if ($this->isDoctrinePDOConnection()) {
2291
      return $this->query('use :database', ['database' => $database]);
2292
    }
2293
2294
    return false;
2295
  }
2296
2297
  /**
2298
   * @param array $extra_config   <p>
2299
   *                              'session_to_db' => false|true<br>
2300
   *                              'socket' => 'string (path)'<br>
2301
   *                              'ssl' => 'bool'<br>
2302
   *                              'clientkey' => 'string (path)'<br>
2303
   *                              'clientcert' => 'string (path)'<br>
2304
   *                              'cacert' => 'string (path)'<br>
2305
   *                              </p>
2306
   */
2307 23
  public function setConfigExtra(array $extra_config)
2308
  {
2309 23
    if (isset($extra_config['session_to_db'])) {
2310
      $this->session_to_db = (boolean)$extra_config['session_to_db'];
2311
    }
2312
2313 23
    if (isset($extra_config['doctrine'])) {
2314
      if ($extra_config['doctrine'] instanceof \Doctrine\DBAL\Connection) {
2315
        $this->doctrine_connection = $extra_config['doctrine'];
2316
      } else {
2317
        throw new DBConnectException('Error "doctrine"-connection is not valid');
2318
      }
2319
    }
2320
2321 23
    if (isset($extra_config['socket'])) {
2322
      $this->socket = $extra_config['socket'];
2323
    }
2324
2325 23
    if (isset($extra_config['ssl'])) {
2326
      $this->ssl = $extra_config['ssl'];
2327
    }
2328
2329 23
    if (isset($extra_config['clientkey'])) {
2330
      $this->clientkey = $extra_config['clientkey'];
2331
    }
2332
2333 23
    if (isset($extra_config['clientcert'])) {
2334
      $this->clientcert = $extra_config['clientcert'];
2335
    }
2336
2337 23
    if (isset($extra_config['cacert'])) {
2338
      $this->cacert = $extra_config['cacert'];
2339
    }
2340 23
  }
2341
2342
  /**
2343
   * Set the current charset.
2344
   *
2345
   * @param string $charset
2346
   *
2347
   * @return bool
2348
   */
2349 14
  public function set_charset(string $charset): bool
2350
  {
2351 14
    $charsetLower = strtolower($charset);
2352 14
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
2353 8
      $charset = 'utf8';
2354
    }
2355 14
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
2356 8
      $charset = 'utf8mb4';
2357
    }
2358
2359 14
    $this->charset = $charset;
2360
2361
    if (
2362 14
        $this->mysqli_link
2363
        &&
2364 14
        $this->mysqli_link instanceof \mysqli
2365
    ) {
2366
2367 14
      $return = mysqli_set_charset($this->mysqli_link, $charset);
2368
2369
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
2370 14
      @\mysqli_query($this->mysqli_link, 'SET CHARACTER SET ' . $charset);
2371
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
2372 14
      @\mysqli_query($this->mysqli_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...
2373
2374
    } elseif ($this->isDoctrinePDOConnection() === true) {
2375
2376
      $doctrineWrappedConnection = $this->getDoctrinePDOConnection();
2377
2378
      $doctrineWrappedConnection->exec('SET CHARACTER SET ' . $charset);
2379
      $doctrineWrappedConnection->exec("SET NAMES '" . $charset . "'");
2380
2381
      $return = true;
2382
2383
    } else {
2384
2385
      throw new DBConnectException('Can not set the charset');
2386
2387
    }
2388
2389 14
    return $return;
2390
  }
2391
2392
  /**
2393
   * Set the option to convert null to "''" (empty string).
2394
   *
2395
   * Used in secure() => select(), insert(), update(), delete()
2396
   *
2397
   * @deprecated It's not recommended to convert NULL into an empty string!
2398
   *
2399
   * @param bool $bool
2400
   *
2401
   * @return self
2402
   */
2403 3
  public function set_convert_null_to_empty_string(bool $bool): self
2404
  {
2405 3
    $this->convert_null_to_empty_string = $bool;
2406
2407 3
    return $this;
2408
  }
2409
2410
  /**
2411
   * Enables or disables internal report functions
2412
   *
2413
   * @link http://php.net/manual/en/function.mysqli-report.php
2414
   *
2415
   * @param int $flags <p>
2416
   *                   <table>
2417
   *                   Supported flags
2418
   *                   <tr valign="top">
2419
   *                   <td>Name</td>
2420
   *                   <td>Description</td>
2421
   *                   </tr>
2422
   *                   <tr valign="top">
2423
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
2424
   *                   <td>Turns reporting off</td>
2425
   *                   </tr>
2426
   *                   <tr valign="top">
2427
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
2428
   *                   <td>Report errors from mysqli function calls</td>
2429
   *                   </tr>
2430
   *                   <tr valign="top">
2431
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
2432
   *                   <td>
2433
   *                   Throw <b>mysqli_sql_exception</b> for errors
2434
   *                   instead of warnings
2435
   *                   </td>
2436
   *                   </tr>
2437
   *                   <tr valign="top">
2438
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
2439
   *                   <td>Report if no index or bad index was used in a query</td>
2440
   *                   </tr>
2441
   *                   <tr valign="top">
2442
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
2443
   *                   <td>Set all options (report all)</td>
2444
   *                   </tr>
2445
   *                   </table>
2446
   *                   </p>
2447
   *
2448
   * @return bool
2449
   */
2450
  public function set_mysqli_report(int $flags): bool
2451
  {
2452
    if (
2453
        $this->mysqli_link
2454
        &&
2455
        $this->mysqli_link instanceof \mysqli
2456
    ) {
2457
      return \mysqli_report($flags);
2458
    }
2459
2460
    return false;
2461
  }
2462
2463
  /**
2464
   * Show config errors by throw exceptions.
2465
   *
2466
   * @return bool
2467
   *
2468
   * @throws \InvalidArgumentException
2469
   */
2470 23
  public function showConfigError(): bool
2471
  {
2472
    // check if a doctrine connection is already open, first
2473
    if (
2474 23
        $this->doctrine_connection
2475
        &&
2476 23
        $this->doctrine_connection->isConnected()
2477
    ) {
2478
      return true;
2479
    }
2480
2481
    if (
2482 23
        !$this->hostname
2483
        ||
2484 20
        !$this->username
2485
        ||
2486 23
        !$this->database
2487
    ) {
2488
2489 9
      if (!$this->hostname) {
2490 3
        throw new \InvalidArgumentException('no-sql-hostname');
2491
      }
2492
2493 6
      if (!$this->username) {
2494 3
        throw new \InvalidArgumentException('no-sql-username');
2495
      }
2496
2497 3
      if (!$this->database) {
2498 3
        throw new \InvalidArgumentException('no-sql-database');
2499
      }
2500
2501
      return false;
2502
    }
2503
2504 14
    return true;
2505
  }
2506
2507
  /**
2508
   * alias: "beginTransaction()"
2509
   */
2510 3
  public function startTransaction(): bool
2511
  {
2512 3
    return $this->beginTransaction();
2513
  }
2514
2515
  /**
2516
   * Determine if database table exists
2517
   *
2518
   * @param string $table
2519
   *
2520
   * @return bool
2521
   */
2522 3
  public function table_exists(string $table): bool
2523
  {
2524 3
    $check = $this->query('SELECT 1 FROM ' . $this->quote_string($table));
2525
2526 3
    return $check !== false
2527
           &&
2528 3
           $check instanceof Result
2529
           &&
2530 3
           $check->num_rows > 0;
2531
  }
2532
2533
  /**
2534
   * Execute a callback inside a transaction.
2535
   *
2536
   * @param callback $callback <p>The callback to run inside the transaction, if it's throws an "Exception" or if it's
2537
   *                           returns "false", all SQL-statements in the callback will be rollbacked.</p>
2538
   *
2539
   * @return bool <p>bool true on success, false otherwise.</p>
2540
   */
2541 3
  public function transact($callback): bool
2542
  {
2543
    try {
2544
2545 3
      $beginTransaction = $this->beginTransaction();
2546 3
      if ($beginTransaction === false) {
2547 3
        $this->debug->displayError('Error: transact -> can not start transaction!', false);
2548
2549 3
        return false;
2550
      }
2551
2552 3
      $result = $callback($this);
2553 3
      if ($result === false) {
2554
        /** @noinspection ThrowRawExceptionInspection */
2555 3
        throw new \Exception('call_user_func [' . $callback . '] === false');
2556
      }
2557
2558 3
      return $this->commit();
2559
2560 3
    } catch (\Exception $e) {
2561
2562 3
      $this->rollback();
2563
2564 3
      return false;
2565
    }
2566
  }
2567
2568
  /**
2569
   * Execute a "update"-query.
2570
   *
2571
   * @param string       $table
2572
   * @param array        $data
2573
   * @param array|string $where
2574
   * @param null|string  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2575
   *
2576
   * @return false|int <p>false on error</p>
2577
   *
2578
   * @throws QueryException
2579
   */
2580 21
  public function update(string $table, array $data = [], $where = '1=1', string $databaseName = null)
2581
  {
2582
    // init
2583 21
    $table = \trim($table);
2584
2585 21
    if ($table === '') {
2586 3
      $this->debug->displayError('Invalid table name, table name in empty.', false);
2587
2588 3
      return false;
2589
    }
2590
2591 21
    if (\count($data) === 0) {
2592 6
      $this->debug->displayError('Invalid data for UPDATE, data is empty.', false);
2593
2594 6
      return false;
2595
    }
2596
2597 21
    $SET = $this->_parseArrayPair($data);
2598
2599 21
    if (\is_string($where)) {
2600 6
      $WHERE = $this->escape($where, false);
2601 18
    } elseif (\is_array($where)) {
2602 15
      $WHERE = $this->_parseArrayPair($where, 'AND');
2603
    } else {
2604 3
      $WHERE = '';
2605
    }
2606
2607 21
    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...
2608
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2609
    }
2610
2611 21
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE)";
2612
2613 21
    return $this->query($sql);
2614
  }
2615
2616
}
2617