Completed
Push — master ( b7af1e...adfd2c )
by Lars
12:23 queued 03:36
created

DB::getInstance()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 46
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 46
ccs 9
cts 9
cp 1
rs 8.4751
c 0
b 0
f 0
cc 5
eloc 26
nc 4
nop 11
crap 5

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace voku\db;
4
5
use voku\cache\Cache;
6
use voku\db\exceptions\DBConnectException;
7
use voku\db\exceptions\DBGoneAwayException;
8
use voku\db\exceptions\QueryException;
9
use voku\helper\UTF8;
10
11
/**
12
 * DB: this handles DB queries via MySQLi
13
 *
14
 * @package voku\db
15
 */
16
final class DB
17
{
18
19
  /**
20
   * @var int
21
   */
22
  public $query_count = 0;
23
24
  /**
25
   * @var \mysqli
26
   */
27
  private $link = false;
28
29
  /**
30
   * @var bool
31
   */
32
  private $connected = false;
33
34
  /**
35
   * @var array
36
   */
37
  private $mysqlDefaultTimeFunctions;
38
39
  /**
40
   * @var string
41
   */
42
  private $hostname = '';
43
44
  /**
45
   * @var string
46
   */
47
  private $username = '';
48
49
  /**
50
   * @var string
51
   */
52
  private $password = '';
53
54
  /**
55
   * @var string
56
   */
57
  private $database = '';
58
59
  /**
60
   * @var int
61
   */
62
  private $port = 3306;
63
64
  /**
65
   * @var string
66
   */
67
  private $charset = 'utf8';
68
69
  /**
70
   * @var string
71
   */
72
  private $socket = '';
73
74
  /**
75
   * @var bool
76
   */
77
  private $session_to_db = false;
78
79
  /**
80
   * @var bool
81
   */
82
  private $_in_transaction = false;
83
84
  /**
85
   * @var bool
86
   */
87
  private $_convert_null_to_empty_string = false;
88
89
  /**
90
   * @var bool
91
   */
92
  private $_ssl = false;
93
  /**
94
   * The path name to the key file
95
   *
96
   * @var string
97
   */
98
  private $_clientkey;
99
  /**
100
   * The path name to the certificate file
101
   *
102
   * @var string
103
   */
104
  private $_clientcert;
105
  /**
106
   * The path name to the certificate authority file
107
   *
108
   * @var string
109 10
   */
110
  private $_cacert;
111 10
112
  /**
113 10
   * @var Debug
114
   */
115 10
  private $_debug;
116 10
117 10
  /**
118 10
   * __construct()
119 10
   *
120 10
   * @param string         $hostname
121 10
   * @param string         $username
122 10
   * @param string         $password
123 10
   * @param string         $database
124 10
   * @param int            $port
125 10
   * @param string         $charset
126
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
127 10
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
128
   * @param string         $logger_class_name
129 7
   * @param string         $logger_level  'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
130
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
131 4
   */
132
  protected function __construct($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
133 4
  {
134
    $this->connected = false;
135 4
136
    $this->_debug = new Debug($this);
137 4
138
    $this->_loadConfig(
139 4
        $hostname,
140
        $username,
141 4
        $password,
142
        $database,
143 4
        $port,
144
        $charset,
145 4
        $exit_on_error,
146
        $echo_on_error,
147 4
        $logger_class_name,
148
        $logger_level,
149 4
        $session_to_db
150
    );
151 4
152
    $this->connect();
153 4
154
    $this->mysqlDefaultTimeFunctions = array(
155 4
      // Returns the current date.
156
      'CURDATE()',
157 4
      // CURRENT_DATE	| Synonyms for CURDATE()
158
      'CURRENT_DATE()',
159 4
      // CURRENT_TIME	| Synonyms for CURTIME()
160
      'CURRENT_TIME()',
161
      // CURRENT_TIMESTAMP | Synonyms for NOW()
162
      'CURRENT_TIMESTAMP()',
163
      // Returns the current time.
164
      'CURTIME()',
165
      // Synonym for NOW()
166
      'LOCALTIME()',
167
      // Synonym for NOW()
168
      'LOCALTIMESTAMP()',
169
      // Returns the current date and time.
170
      'NOW()',
171
      // Returns the time at which the function executes.
172
      'SYSDATE()',
173
      // Returns a UNIX timestamp.
174
      'UNIX_TIMESTAMP()',
175
      // Returns the current UTC date.
176
      'UTC_DATE()',
177
      // Returns the current UTC time.
178 10
      'UTC_TIME()',
179
      // Returns the current UTC date and time.
180 10
      'UTC_TIMESTAMP()',
181 10
    );
182 10
  }
183 10
184
  /**
185 10
   * Prevent the instance from being cloned.
186 4
   *
187 4
   * @return void
188
   */
189 10
  private function __clone()
190 4
  {
191 4
  }
192
193
  /**
194 7
   * __destruct
195
   *
196
   */
197
  public function __destruct()
198 10
  {
199
    // close the connection only if we don't save PHP-SESSION's in DB
200
    if ($this->session_to_db === false) {
201
      $this->close();
202 10
    }
203
  }
204 10
205 10
  /**
206
   * __wakeup
207 10
   *
208 10
   * @return void
209 10
   */
210
  public function __wakeup()
211 10
  {
212 10
    $this->reconnect();
213 10
  }
214
215 10
  /**
216 10
   * Load the config from the constructor.
217
   *
218 10
   * @param string         $hostname
219
   * @param string         $username
220 10
   * @param string         $password
221
   * @param string         $database
222
   * @param int            $port
223
   * @param string         $charset
224
   * @param boolean|string $exit_on_error <p>Use a empty string "" or false to disable it.</p>
225
   * @param boolean|string $echo_on_error <p>Use a empty string "" or false to disable it.</p>
226
   * @param string         $logger_class_name
227
   * @param string         $logger_level
228
   * @param boolean|string $session_to_db <p>Use a empty string "" or false to disable it.</p>
229
   *
230 10
   * @return bool
231
   */
232
  private function _loadConfig($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
233
  {
234 10
    $this->hostname = (string)$hostname;
235 10
    $this->username = (string)$username;
236 9
    $this->password = (string)$password;
237 9
    $this->database = (string)$database;
238 8
239 10
    if ($charset) {
240
      $this->charset = (string)$charset;
241 3
    }
242 1
243
    if ($port) {
244
      $this->port = (int)$port;
245 2
    } else {
246 1
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
247
      /** @noinspection UsageOfSilenceOperatorInspection */
248
      $this->port = (int)@ini_get('mysqli.default_port');
249 1
    }
250 1
251
    // fallback
252
    if (!$this->port) {
253
      $this->port = 3306;
254
    }
255
256 7
    if (!$this->socket) {
257
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
258
      $this->socket = @ini_get('mysqli.default_socket');
259
    }
260
261
    if ($exit_on_error === true || $exit_on_error === false) {
262
      $this->_debug->setExitOnError($exit_on_error);
263
    }
264
265
    if ($echo_on_error === true || $echo_on_error === false) {
266 9
      $this->_debug->setEchoOnError($echo_on_error);
267
    }
268 9
269 1
    $this->_debug->setLoggerClassName($logger_class_name);
270
    $this->_debug->setLoggerLevel($logger_level);
271
272 9
    $this->session_to_db = (boolean)$session_to_db;
273
274 9
    return $this->showConfigError();
275
  }
276 9
277 9
  /**
278 9
   * Parses arrays with value pairs and generates SQL to use in queries.
279
   *
280
   * @param array  $arrayPair
281 9
   * @param string $glue <p>This is the separator.</p>
282 9
   *
283 9
   * @return string
284 9
   *
285 9
   * @internal
286 9
   */
287 9
  public function _parseArrayPair($arrayPair, $glue = ',')
288 9
  {
289 9
    // init
290 9
    $sql = '';
291 3
292 3
    /** @noinspection IsEmptyFunctionUsageInspection */
293 3
    if (empty($arrayPair)) {
294
      return '';
295 6
    }
296
297 6
    $arrayPairCounter = 0;
298
    foreach ($arrayPair as $_key => $_value) {
299
      $_connector = '=';
300
      $_glueHelper = '';
301
      $_key_upper = strtoupper($_key);
302
303
      if (strpos($_key_upper, ' NOT') !== false) {
304 6
        $_connector = 'NOT';
305
      }
306 6
307
      if (strpos($_key_upper, ' IS') !== false) {
308
        $_connector = 'IS';
309
      }
310
311
      if (strpos($_key_upper, ' IS NOT') !== false) {
312
        $_connector = 'IS NOT';
313
      }
314 45
315
      if (strpos($_key_upper, ' IN') !== false) {
316 45
        $_connector = 'IN';
317
      }
318
319
      if (strpos($_key_upper, ' NOT IN') !== false) {
320
        $_connector = 'NOT IN';
321
      }
322
323
      if (strpos($_key_upper, ' BETWEEN') !== false) {
324
        $_connector = 'BETWEEN';
325
      }
326 2
327
      if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
328 2
        $_connector = 'NOT BETWEEN';
329
      }
330
331
      if (strpos($_key_upper, ' LIKE') !== false) {
332
        $_connector = 'LIKE';
333
      }
334
335
      if (strpos($_key_upper, ' NOT LIKE') !== false) {
336
        $_connector = 'NOT LIKE';
337
      }
338
339 View Code Duplication
      if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
340
        $_connector = '>';
341
      }
342
343 View Code Duplication
      if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
344
        $_connector = '<';
345
      }
346
347
      if (strpos($_key_upper, ' >=') !== false) {
348
        $_connector = '>=';
349
      }
350
351
      if (strpos($_key_upper, ' <=') !== false) {
352
        $_connector = '<=';
353
      }
354
355
      if (strpos($_key_upper, ' <>') !== false) {
356
        $_connector = '<>';
357
      }
358
359
      if (strpos($_key_upper, ' OR') !== false) {
360
        $_glueHelper = 'OR';
361
      }
362
363
      if (strpos($_key_upper, ' AND') !== false) {
364
        $_glueHelper = 'AND';
365
      }
366
367
      if (is_array($_value) === true) {
368
        foreach ($_value as $oldKey => $oldValue) {
369
          $_value[$oldKey] = $this->secure($oldValue);
370
        }
371
372
        if ($_connector === 'NOT IN' || $_connector === 'IN') {
373
          $_value = '(' . implode(',', $_value) . ')';
374
        } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
375
          $_value = '(' . implode(' AND ', $_value) . ')';
376
        }
377
378
      } else {
379
        $_value = $this->secure($_value);
380
      }
381
382
      $quoteString = $this->quote_string(
383 57
          trim(
384
              str_ireplace(
385
                  array(
386
                      $_connector,
387
                      $_glueHelper,
388 57
                  ),
389
                  '',
390
                  $_key
391
              )
392
          )
393 57
      );
394
395
      if (!is_array($_value)) {
396 57
        $_value = array($_value);
397 57
      }
398 11
399 57
      if (!$_glueHelper) {
400 11
        $_glueHelper = $glue;
401
      }
402
403 57
      $tmpCounter = 0;
404 57
      foreach ($_value as $valueInner) {
405 57
406
        $_glueHelperInner = $_glueHelper;
407 57
408 10
        if ($arrayPairCounter === 0) {
409 10
410 10
          if ($tmpCounter === 0 && $_glueHelper === 'OR') {
411 10
            $_glueHelperInner = '1 = 1 AND ('; // first "OR"-query glue
412 10
          } elseif ($tmpCounter === 0) {
413 10
            $_glueHelperInner = ''; // first query glue e.g. for "INSERT"-query -> skip the first ","
414 10
          }
415 10
416 10
        } elseif ($tmpCounter === 0 && $_glueHelper === 'OR') {
417 10
          $_glueHelperInner = 'AND ('; // inner-loop "OR"-query glue
418 10
        }
419
420 10
        $sql .= ' ' . $_glueHelperInner . ' ' . $quoteString . ' ' . $_connector . ' ' . $valueInner . " \n";
421
        $tmpCounter++;
422 4
      }
423 1
424 1
      if ($_glueHelper === 'OR') {
425 4
        $sql .= ' ) ';
426
      }
427 57
428
      $arrayPairCounter++;
429
    }
430
431
    return $sql;
432
  }
433
434
  /**
435
   * _parseQueryParams
436
   *
437
   * @param string $sql
438
   * @param array  $params
439
   *
440
   * @return string
441
   */
442
  private function _parseQueryParams($sql, array $params)
443
  {
444
    // is there anything to parse?
445
    if (strpos($sql, '?') === false) {
446
      return $sql;
447
    }
448
449
    if (count($params) > 0) {
450 35
      $parseKey = md5(uniqid((string)mt_rand(), true));
451
      $sql = str_replace('?', $parseKey, $sql);
452 35
453
      $k = 0;
454
      while (strpos($sql, $parseKey) !== false) {
455
        $value = $this->secure($params[$k]);
456 35
        $sql = UTF8::str_replace_first($parseKey, $value, $sql);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by \voku\helper\UTF8::str_r...parseKey, $value, $sql) on line 456 can also be of type array<integer,string>; however, voku\helper\UTF8::str_replace_first() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
457 4
        $k++;
458
      }
459 4
    }
460
461
    return $sql;
462
  }
463
464 33
  /**
465 3
   * Gets the number of affected rows in a previous MySQL operation.
466 33
   *
467 3
   * @return int
468 33
   */
469 3
  public function affected_rows()
470 3
  {
471
    return \mysqli_affected_rows($this->link);
472 33
  }
473 33
474 33
  /**
475
   * Begins a transaction, by turning off auto commit.
476 33
   *
477
   * @return bool <p>This will return true or false indicating success of transaction</p>
478 33
   */
479 33
  public function beginTransaction()
480 28
  {
481 28
    if ($this->_in_transaction === true) {
482 24
      $this->_debug->displayError('Error mysql server already in transaction!', false);
483
      return false;
484
    }
485 33
486
    $this->clearErrors(); // needed for "$this->endTransaction()"
487
    $this->_in_transaction = true;
488 27
    $return = \mysqli_autocommit($this->link, false);
489
    if ($return === false) {
490
      $this->_in_transaction = false;
491 27
    }
492
493
    return $return;
494
  }
495 25
496
497
  /**
498 23
   * Clear the errors in "_debug->_errors".
499 22
   *
500 22
   * @return bool
501
   */
502 22
  public function clearErrors()
503
  {
504
    return $this->_debug->clearErrors();
505
  }
506 8
507 8
  /**
508 8
   * Closes a previously opened database connection.
509
   *
510 8
   * @return bool
511
   */
512
  public function close()
513
  {
514
    $this->connected = false;
515
    if (!$this->link) {
516
      return false;
517
    }
518
519
    if (\mysqli_close($this->link)) {
520 8
      $this->link = null;
521
      return true;
522 8
    }
523
524
    return false;
525
  }
526
527
  /**
528
   * Open a new connection to the MySQL server.
529
   *
530
   * @return bool
531
   *
532
   * @throws DBConnectException
533 3
   */
534
  public function connect()
535
  {
536 3
    if ($this->isReady()) {
537
      return true;
538
    }
539
540 3
    $flags = null;
541 3
542 3
    \mysqli_report(MYSQLI_REPORT_STRICT);
543
    try {
544 3
      $this->link = \mysqli_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like \mysqli_init() of type object<mysql> is incompatible with the declared type object<mysqli> of property $link.

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

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

Loading history...
545 3
546 3
      if (Helper::isMysqlndIsUsed() === true) {
547 3
        \mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
548 3
      }
549 3
550 3
      if ($this->_ssl === true) {
551
552 3
        if (empty($this->clientcert)) {
0 ignored issues
show
Bug introduced by
The property clientcert does not seem to exist. Did you mean _clientcert?

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

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

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

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

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

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

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

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

Loading history...
561
          throw new DBConnectException('Error connecting to mysql server: cacert not defined');
562
        }
563
564
        \mysqli_options($this->link, MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
565
        \mysqli_ssl_set(
566
            $this->link,
567
            $this->_clientkey,
568
            $this->_clientcert,
569
            $this->_cacert,
570
            null,
571
            null
572
        );
573
        $flags = MYSQLI_CLIENT_SSL;
574
      }
575
576
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
577
      $this->connected = @\mysqli_real_connect(
578
          $this->link,
579
          $this->hostname,
580
          $this->username,
581
          $this->password,
582
          $this->database,
583
          $this->port,
584
          $this->socket,
585
          $flags
586
      );
587
    } catch (\Exception $e) {
588
      $error = 'Error connecting to mysql server: ' . $e->getMessage();
589
      $this->_debug->displayError($error, false);
590
      throw new DBConnectException($error, 100, $e);
591
    }
592
    \mysqli_report(MYSQLI_REPORT_OFF);
593
594
    $errno = mysqli_connect_errno();
595
    if (!$this->connected || $errno) {
596
      $error = 'Error connecting to mysql server: ' . \mysqli_connect_error() . ' (' . $errno . ')';
597 26
      $this->_debug->displayError($error, false);
598
      throw new DBConnectException($error, 101);
599
    }
600
601 26
    $this->set_charset($this->charset);
602 26
603 26
    return $this->isReady();
604 1
  }
605
606
  /**
607 26
   * Execute a "delete"-query.
608 1
   *
609
   * @param string       $table
610
   * @param string|array $where
611 26
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
612 22
   *
613 22
   * @return false|int <p>false on error</p>
614
   *
615 26
   *    * @throws QueryException
616
   */
617 26 View Code Duplication
  public function delete($table, $where, $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...
618 22
  {
619 22
    // init
620
    $table = trim($table);
621 26
622
    if ($table === '') {
623
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
624
625
      return false;
626
    }
627
628
    if (is_string($where)) {
629
      $WHERE = $this->escape($where, false);
630
    } elseif (is_array($where)) {
631
      $WHERE = $this->_parseArrayPair($where, 'AND');
632
    } else {
633
      $WHERE = '';
634
    }
635
636
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type string|null 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...
637
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
638
    }
639
640
    $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
641 33
642
    return $this->query($sql);
643 33
  }
644 2
645
  /**
646
   * Ends a transaction and commits if no errors, then ends autocommit.
647 33
   *
648 2
   * @return bool <p>This will return true or false indicating success of transactions.</p>
649
   */
650
  public function endTransaction()
651
  {
652 33
653 33
    if (!$this->errors()) {
654 33
      \mysqli_commit($this->link);
655
      $return = true;
656
    } else {
657
      $this->rollback();
658 33
      $return = false;
659
    }
660 33
661 33
    \mysqli_autocommit($this->link, true);
662
    $this->_in_transaction = false;
663 33
664 33
    return $return;
665 33
  }
666 33
667
  /**
668 33
   * Get all errors from "$this->_errors".
669 33
   *
670
   * @return array|false <p>false === on errors</p>
671
   */
672
  public function errors()
673 24
  {
674
    $errors = $this->_debug->getErrors();
675
676 33
    return count($errors) > 0 ? $errors : false;
677
  }
678
679
  /**
680 5
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
681
   *
682
   * @param mixed     $var           boolean: convert into "integer"<br />
683 33
   *                                 int: int (don't change it)<br />
684
   *                                 float: float (don't change it)<br />
685
   *                                 null: null (don't change it)<br />
686
   *                                 array: run escape() for every key => value<br />
687 3
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
688 3
   * @param bool      $stripe_non_utf8
689
   * @param bool      $html_entity_decode
690
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
691 1
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
692 1
   *                                 <strong>null</strong> => Convert the array into null, every time.
693
   *
694 1
   * @return mixed
695 1
   */
696
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
697
  {
698 1
    if ($var === '') {
699 1
      return '';
700
    }
701 1
702 1
    if ($var === null) {
703
      return null;
704 1
    }
705
706
    // save the current value as int (for later usage)
707 1
    if (!is_object($var)) {
708
      $varInt = (int)$var;
709
    }
710
711 33
    /** @noinspection TypeUnsafeComparisonInspection */
712
    if (
713
        is_int($var)
714 3
        ||
715 3
        is_bool($var)
716 3
        ||
717 3
        (
718 33
            isset($varInt, $var[0])
719
            &&
720
            $var[0] != '0'
721
            &&
722 33
            "$varInt" == $var
0 ignored issues
show
Bug introduced by
The variable $varInt 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...
723
        )
724 33
    ) {
725 9
726 9
      // "int" || int || bool
727
728 33
      return (int)$var;
729
    }
730 1
731 1
    if (is_float($var)) {
732
733 33
      // float
734
735 33
      return $var;
736
    }
737 33
738
    if (is_array($var)) {
739
740
      // array
741 3
742
      if ($convert_array === null) {
743
        return null;
744
      }
745
746 3
      $varCleaned = array();
747
      foreach ((array)$var as $key => $value) {
748
749
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
750
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
751
752 2
        /** @noinspection OffsetOperationsInspection */
753
        $varCleaned[$key] = $value;
754
      }
755
756
      if ($convert_array === true) {
757
        $varCleaned = implode(',', $varCleaned);
758
759
        return $varCleaned;
760
      }
761 35
762
      return (array)$varCleaned;
763 35
    }
764
765
    if (
766
        is_string($var)
767
        ||
768
        (
769
            is_object($var)
770
            &&
771 22
            method_exists($var, '__toString')
772
        )
773 22
    ) {
774
775
      // "string"
776
777
      $var = (string)$var;
778
779
      if ($stripe_non_utf8 === true) {
780
        $var = UTF8::cleanup($var);
781 8
      }
782
783 8
      if ($html_entity_decode === true) {
784
        // use no-html-entity for db
785
        $var = UTF8::html_entity_decode($var);
786
      }
787
788
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
789
790
      $var = \mysqli_real_escape_string($this->getLink(), $var);
791
792
      return (string)$var;
793
794
    }
795
796
    if ($var instanceof \DateTime) {
797
798 9
      // "DateTime"-object
799
800 9
      try {
801 1
        return $this->escape($var->format('Y-m-d H:i:s'), false);
802
      } catch (\Exception $e) {
803
        return null;
804 1
      }
805
806
    } else {
807
      return false;
808
    }
809 1
  }
810
811
  /**
812 1
   * Execute select/insert/update/delete sql-queries.
813 1
   *
814
   * @param string $query    sql-query
815
   * @param bool   $useCache use cache?
816 1
   * @param int    $cacheTTL cache-ttl in seconds
817
   *
818
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
819 8
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
820
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
821
   *               "true" by e.g. "DROP"-queries<br />
822 8
   *               "false" on error
823
   *
824 8
   * @throws QueryException
825
   */
826
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
827
  {
828
    // init
829
    $cacheKey = null;
830
    $db = self::getInstance();
831
832 View Code Duplication
    if ($useCache === true) {
833
      $cache = new Cache(null, null, false, $useCache);
834 3
      $cacheKey = 'sql-' . md5($query);
835
836 3
      if (
837
          $cache->getCacheIsReady() === true
838 3
          &&
839 2
          $cache->existsItem($cacheKey)
840 2
      ) {
841
        return $cache->getItem($cacheKey);
842 3
      }
843 3
844 3
    } else {
845 3
      $cache = false;
846
    }
847 3
848
    $result = $db->query($query);
849
850
    if ($result instanceof Result) {
851
852
      $return = $result->fetchAllArray();
853
854
      // save into the cache
855 View Code Duplication
      if (
856 3
          $cacheKey !== null
857
          &&
858
          $useCache === true
859 3
          &&
860 3
          $cache instanceof Cache
861 3
          &&
862 3
          $cache->getCacheIsReady() === true
863
      ) {
864
        $cache->setItem($cacheKey, $return, $cacheTTL);
865 3
      }
866
867
    } else {
868
      $return = $result;
869
    }
870
871
    return $return;
872
  }
873
874
  /**
875
   * Get all table-names via "SHOW TABLES".
876
   *
877
   * @return array
878
   */
879
  public function getAllTables()
880
  {
881
    $query = 'SHOW TABLES';
882
    $result = $this->query($query);
883
884
    return $result->fetchAllArray();
885
  }
886 3
887
  /**
888
   * @return Debug
889 3
   */
890 3
  public function getDebugger()
891
  {
892 3
    return $this->_debug;
893 1
  }
894 1
895
  /**
896
   * Get errors from "$this->_errors".
897 1
   *
898 1
   * @return array
899 1
   */
900 1
  public function getErrors()
901 1
  {
902
    return $this->_debug->getErrors();
903
  }
904 1
905 3
  /**
906
   * getInstance()
907
   *
908 3
   * @param string      $hostname
909
   * @param string      $username
910 3
   * @param string      $password
911
   * @param string      $database
912 1
   * @param string      $port          default is (int)3306
913
   * @param string      $charset       default is 'utf8' or 'utf8mb4' (if supported)
914
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
915
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
916
   * @param string      $logger_class_name
917 1
   * @param string      $logger_level
918
   * @param bool|string $session_to_db use a empty string "" or false to disable it
919 1
   *
920
   * @return \voku\db\DB
921 1
   */
922 1
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
923 1
  {
924 1
    /**
925 1
     * @var $instance DB[]
926
     */
927 1
    static $instance = array();
928 2
929
    /**
930
     * @var $firstInstance DB
931 3
     */
932
    static $firstInstance = null;
933
934
    if (
935
        $hostname . $username . $password . $database . $port . $charset == ''
936
        &&
937
        null !== $firstInstance
938
    ) {
939 1
      return $firstInstance;
940
    }
941 1
942
    $connection = md5(
943
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
944
    );
945
946
    if (!isset($instance[$connection])) {
947
      $instance[$connection] = new self(
948
          $hostname,
949
          $username,
950
          $password,
951
          $database,
952
          $port,
953
          $charset,
954
          $exit_on_error,
955
          $echo_on_error,
956
          $logger_class_name,
957
          $logger_level,
958
          $session_to_db
959
      );
960
961
      if (null === $firstInstance) {
962
        $firstInstance = $instance[$connection];
963
      }
964
    }
965
966
    return $instance[$connection];
967
  }
968
969
  /**
970
   * Get the mysqli-link (link identifier returned by mysqli-connect).
971
   *
972
   * @return \mysqli
973
   */
974
  public function getLink()
975
  {
976
    return $this->link;
977
  }
978
979
  /**
980
   * Get the current charset.
981
   *
982
   * @return string
983
   */
984
  public function get_charset()
985
  {
986
    return $this->charset;
987
  }
988
989
  /**
990
   * Check if we are in a transaction.
991
   *
992
   * @return bool
993
   */
994
  public function inTransaction()
995
  {
996 7
    return $this->_in_transaction;
997
  }
998 7
999 7
  /**
1000 5
   * Execute a "insert"-query.
1001 5
   *
1002 7
   * @param string      $table
1003 5
   * @param array       $data
1004 5
   * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1005
   *
1006 7
   * @return false|int <p>false on error</p>
1007
   *
1008 7
   * @throws QueryException
1009
   */
1010
  public function insert($table, array $data = array(), $databaseName = null)
1011 7
  {
1012
    // init
1013
    $table = trim($table);
1014 7
1015
    if ($table === '') {
1016 7
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1017
1018
      return false;
1019
    }
1020
1021
    if (count($data) === 0) {
1022
      $this->_debug->displayError('Invalid data for INSERT, data is empty.', false);
1023
1024
      return false;
1025
    }
1026 1
1027
    $SET = $this->_parseArrayPair($data);
1028 1
1029 1
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type string|null 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...
1030
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1031
    }
1032
1033
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET;";
1034
1035
    return $this->query($sql);
1036 1
  }
1037
1038 1
  /**
1039 1
   * Returns the auto generated id used in the last query.
1040
   *
1041 1
   * @return int|string
1042
   */
1043
  public function insert_id()
1044
  {
1045
    return \mysqli_insert_id($this->link);
1046
  }
1047
1048
  /**
1049
   * Check if db-connection is ready.
1050
   *
1051
   * @return boolean
1052
   */
1053
  public function isReady()
1054
  {
1055
    return $this->connected ? true : false;
1056 1
  }
1057
1058 1
  /**
1059
   * Get the last sql-error.
1060
   *
1061
   * @return string|false <p>false === there was no error</p>
1062 1
   */
1063 1
  public function lastError()
1064
  {
1065 1
    $errors = $this->_debug->getErrors();
1066
1067
    return count($errors) > 0 ? end($errors) : false;
1068 1
  }
1069 1
1070 1
  /**
1071
   * Execute a sql-multi-query.
1072 1
   *
1073
   * @param string $sql
1074 1
   *
1075 1
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1076 1
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1077
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1078 1
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1079
   *
1080 1
   * @throws QueryException
1081 1
   */
1082 1
  public function multi_query($sql)
1083 1
  {
1084 1
    if (!$this->isReady()) {
1085
      return false;
1086
    }
1087 1
1088 1 View Code Duplication
    if (!$sql || $sql === '') {
1089 1
      $this->_debug->displayError('Can not execute an empty query.', false);
1090
1091
      return false;
1092
    }
1093 1
1094
    $query_start_time = microtime(true);
1095 1
    $resultTmp = \mysqli_multi_query($this->link, $sql);
1096
    $query_duration = microtime(true) - $query_start_time;
1097
1098
    $this->_debug->logQuery($sql, $query_duration, 0);
1099
1100
    $returnTheResult = false;
1101
    $result = array();
1102
    if ($resultTmp) {
1103
      do {
1104
        $resultTmpInner = \mysqli_store_result($this->link);
1105
1106
        if ($resultTmpInner instanceof \mysqli_result) {
1107
          $returnTheResult = true;
1108 1
          $result[] = new Result($sql, $resultTmpInner);
1109 1
        } else {
1110
          $errorMsg = \mysqli_error($this->link);
1111
1112 1
          // is the query successful
1113 1
          if ($resultTmpInner === true || !$errorMsg) {
1114
            $result[] = true;
1115
          } else {
1116
            $result[] = $this->queryErrorHandling($errorMsg, $sql);
1117
          }
1118
        }
1119
      } while (\mysqli_more_results($this->link) === true ? \mysqli_next_result($this->link) : false);
1120
1121
    } else {
1122 1
1123
      $errorMsg = \mysqli_error($this->link);
1124 1
1125 1
      if ($this->_debug->checkForDev() === true) {
1126
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1127
        echo 'Error:' . $errorMsg;
1128
      }
1129
1130
      $this->_debug->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1131
    }
1132 4
1133
    // return the result only if there was a "SELECT"-query
1134 4
    if ($returnTheResult === true) {
1135
      return $result;
1136 4
    }
1137 1
1138
    if (in_array(false, $result, true) === false) {
1139 1
      return true;
1140
    }
1141
1142 4
    return false;
1143
  }
1144
1145
  /**
1146
   * Pings a server connection, or tries to reconnect
1147
   * if the connection has gone down.
1148 4
   *
1149 4
   * @return boolean
1150
   */
1151 4
  public function ping()
1152
  {
1153
    if (
1154
        $this->link
1155
        &&
1156
        $this->link instanceof \mysqli
1157
    ) {
1158
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1159 4
      /** @noinspection UsageOfSilenceOperatorInspection */
1160
      return (bool)@\mysqli_ping($this->link);
1161 4
    }
1162
1163
    return false;
1164
  }
1165
1166
  /**
1167
   * Get a new "Prepare"-Object for your sql-query.
1168
   *
1169 4
   * @param string $query
1170
   *
1171 4
   * @return Prepare
1172
   */
1173
  public function prepare($query)
1174
  {
1175
    return new Prepare($this, $query);
1176
  }
1177
1178
  /**
1179 2
   * Execute a sql-query and return the result-array for select-statements.
1180
   *
1181
   * @param $query
1182 2
   *
1183 1
   * @return mixed
1184 1
   * @deprecated
1185 1
   * @throws \Exception
1186 1
   */
1187 1
  public static function qry($query)
1188
  {
1189
    $db = self::getInstance();
1190 2
1191 2
    $args = func_get_args();
1192
    /** @noinspection SuspiciousAssignmentsInspection */
1193 2
    $query = array_shift($args);
1194
    $query = str_replace('?', '%s', $query);
1195
    $args = array_map(
1196
        array(
1197
            $db,
1198
            'escape',
1199
        ),
1200
        $args
1201 2
    );
1202
    array_unshift($args, $query);
1203 2
    $query = call_user_func_array('sprintf', $args);
1204
    $result = $db->query($query);
1205 2
1206
    if ($result instanceof Result) {
1207
      return $result->fetchAllArray();
1208
    }
1209
1210
    return $result;
1211 2
  }
1212
1213
  /**
1214 2
   * Execute a sql-query.
1215
   *
1216 2
   * @param string        $sql            <p>The sql query-string.</p>
1217 2
   *
1218 2
   * @param array|boolean $params         <p>
1219 2
   *                                      "array" of sql-query-parameters<br/>
1220 2
   *                                      "false" if you don't need any parameter (default)<br/>
1221
   *                                      </p>
1222 2
   *
1223
   * @return bool|int|Result              <p>
1224
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
1225
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
1226
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1227
   *                                      "true" by e.g. "DROP"-queries<br />
1228
   *                                      "false" on error
1229
   *                                      </p>
1230
   *
1231
   * @throws QueryException
1232
   */
1233
  public function query($sql = '', $params = false)
1234
  {
1235
    if (!$this->isReady()) {
1236 21
      return false;
1237
    }
1238
1239 21 View Code Duplication
    if (!$sql || $sql === '') {
1240
      $this->_debug->displayError('Can not execute an empty query.', false);
1241 21
1242 2
      return false;
1243
    }
1244 2
1245
    if (
1246
        $params !== false
1247 20
        &&
1248 3
        is_array($params)
1249
        &&
1250 3
        count($params) > 0
1251
    ) {
1252
      $sql = $this->_parseQueryParams($sql, $params);
1253 18
    }
1254
1255 18
    $query_start_time = microtime(true);
1256
    $query_result = \mysqli_real_query($this->link, $sql);
1257
    $query_duration = microtime(true) - $query_start_time;
1258
1259 18
    $this->query_count++;
1260
1261 18
    $mysqli_field_count = \mysqli_field_count($this->link);
1262
    if ($mysqli_field_count) {
1263
      $result = \mysqli_store_result($this->link);
1264
    } else {
1265
      $result = $query_result;
1266
    }
1267
1268
    if ($result instanceof \mysqli_result) {
1269
1270
      // log the select query
1271
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1252 can also be of type array<integer,string>; however, voku\db\Debug::logQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1272
1273
      // return query result object
1274 23
      return new Result($sql, $result);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1252 can also be of type array<integer,string>; however, voku\db\Result::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1275
1276
    }
1277 23
1278
    if ($query_result === true) {
1279
1280 23
      // "INSERT" || "REPLACE"
1281 View Code Duplication
      if (preg_match('/^\s*"?(INSERT|REPLACE)\s+/i', $sql)) {
1282
        $insert_id = (int)$this->insert_id();
1283
        $this->_debug->logQuery($sql, $query_duration, $insert_id);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1252 can also be of type array<integer,string>; however, voku\db\Debug::logQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1284 23
1285 23
        return $insert_id;
1286 23
      }
1287 23
1288 23
      // "UPDATE" || "DELETE"
1289 View Code Duplication
      if (preg_match('/^\s*"?(UPDATE|DELETE)\s+/i', $sql)) {
1290 23
        $affected_rows = (int)$this->affected_rows();
1291 2
        $this->_debug->logQuery($sql, $query_duration, $affected_rows);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1252 can also be of type array<integer,string>; however, voku\db\Debug::logQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1292 2
1293
        return $affected_rows;
1294 23
      }
1295 1
1296 1
      // log the ? query
1297
      $this->_debug->logQuery($sql, $query_duration, 0);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1252 can also be of type array<integer,string>; however, voku\db\Debug::logQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1298 23
1299 1
      return true;
1300 1
    }
1301
1302 23
    // log the error query
1303 1
    $this->_debug->logQuery($sql, $query_duration, 0, true);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1252 can also be of type array<integer,string>; however, voku\db\Debug::logQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1304 1
1305
    return $this->queryErrorHandling(\mysqli_error($this->link), $sql, $params);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1252 can also be of type array<integer,string>; however, voku\db\DB::queryErrorHandling() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1306 23
  }
1307 1
1308 1
  /**
1309
   * Error-handling for the sql-query.
1310 23
   *
1311 1
   * @param string     $errorMsg
1312 1
   * @param string     $sql
1313
   * @param array|bool $sqlParams false if there wasn't any parameter
1314 23
   *
1315 1
   * @throws QueryException
1316 1
   * @throws DBGoneAwayException
1317
   *
1318 23
   * @return bool
1319 2
   */
1320 2 View Code Duplication
  protected function queryErrorHandling($errorMsg, $sql, $sqlParams = false)
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...
1321
  {
1322 23
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
1323 2
      static $RECONNECT_COUNTER;
1324 2
1325
      // exit if we have more then 3 "DB server has gone away"-errors
1326 23
      if ($RECONNECT_COUNTER > 3) {
1327 4
        $this->_debug->mailToAdmin('DB-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
1328 4
        throw new DBGoneAwayException($errorMsg);
1329
      }
1330 23
1331 1
      $this->_debug->mailToAdmin('DB-Error', $errorMsg . ":\n<br />" . $sql);
1332 1
1333
      // reconnect
1334 23
      $RECONNECT_COUNTER++;
1335 4
      $this->reconnect(true);
1336 4
1337
      // re-run the current query
1338 23
      return $this->query($sql, $sqlParams);
1339 1
    }
1340 1
1341
    $this->_debug->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
1342 23
1343 1
    // this query returned an error, we must display it (only for dev) !!!
1344 1
    $this->_debug->displayError($errorMsg . ' | ' . $sql);
1345
1346 23
    return false;
1347 2
  }
1348 2
1349
  /**
1350 23
   * Quote && Escape e.g. a table name string.
1351 1
   *
1352 1
   * @param string $str
1353
   *
1354 23
   * @return string
1355 2
   */
1356 2
  public function quote_string($str)
1357 2
  {
1358
    $str = str_replace(
1359 2
        '`',
1360 1
        '``',
1361 2
        trim(
1362 1
            $this->escape($str, false),
1363 1
            '`'
1364
        )
1365 2
    );
1366 23
1367
    return '`' . $str . '`';
1368
  }
1369 23
1370 23
  /**
1371 23
   * Reconnect to the MySQL-Server.
1372
   *
1373 23
   * @param bool $checkViaPing
1374 23
   *
1375 23
   * @return bool
1376 23
   */
1377
  public function reconnect($checkViaPing = false)
1378 23
  {
1379 23
    $ping = false;
1380 23
1381
    if ($checkViaPing === true) {
1382 23
      $ping = $this->ping();
1383 23
    }
1384 23
1385
    if ($ping !== true) {
1386 23
      $this->connected = false;
1387 23
      $this->connect();
1388 23
    }
1389
1390 23
    return $this->isReady();
1391 23
  }
1392
1393 23
  /**
1394
   * Execute a "replace"-query.
1395 23
   *
1396
   * @param string      $table
1397 23
   * @param array       $data
1398 1
   * @param null|string $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1399 23
   *
1400 23
   * @return false|int <p>false on error</p>
1401 23
   *
1402
   * @throws QueryException
1403 23
   */
1404 1
  public function replace($table, array $data = array(), $databaseName = null)
1405 1
  {
1406
    // init
1407 23
    $table = trim($table);
1408 23
1409 23
    if ($table === '') {
1410
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1411 23
1412 2
      return false;
1413 2
    }
1414
1415 23
    if (count($data) === 0) {
1416 23
      $this->_debug->displayError('Invalid data for REPLACE, data is empty.', false);
1417
1418 23
      return false;
1419
    }
1420
1421
    // extracting column names
1422
    $columns = array_keys($data);
1423
    foreach ($columns as $k => $_key) {
1424
      /** @noinspection AlterInForeachInspection */
1425
      $columns[$k] = $this->quote_string($_key);
1426
    }
1427
1428 26
    $columns = implode(',', $columns);
1429
1430 26
    // extracting values
1431 26
    foreach ($data as $k => $_value) {
1432 26
      /** @noinspection AlterInForeachInspection */
1433 26
      $data[$k] = $this->secure($_value);
1434 26
    }
1435
    $values = implode(',', $data);
1436 26
1437 26
    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...
1438
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1439 26
    }
1440
1441
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values);";
1442
1443
    return $this->query($sql);
1444
  }
1445
1446
  /**
1447 1
   * Rollback in a transaction.
1448
   */
1449 1
  public function rollback()
1450
  {
1451
    // init
1452
    $return = false;
1453
1454
    if ($this->_in_transaction === true) {
1455
      $return = \mysqli_rollback($this->link);
1456
      \mysqli_autocommit($this->link, true);
1457
      $this->_in_transaction = false;
1458
    }
1459
1460
    return $return;
1461
  }
1462
1463 1
  /**
1464
   * Try to secure a variable, so can you use it in sql-queries.
1465
   *
1466 1
   * <p>
1467
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
1468 1
   * 1. parse into "int"
1469 1
   * </p><br />
1470
   *
1471 1
   * <p>
1472
   * <strong>float:</strong><br />
1473
   * 1. return "float"
1474 1
   * </p><br />
1475 1
   *
1476
   * <p>
1477 1
   * <strong>string:</strong><br />
1478
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
1479
   * 2. trim whitespace<br />
1480
   * 3. trim '<br />
1481 1
   * 4. escape the string (and remove non utf-8 chars)<br />
1482 1
   * 5. trim ' again (because we maybe removed some chars)<br />
1483
   * 6. add ' around the new string<br />
1484 1
   * </p><br />
1485 1
   *
1486
   * <p>
1487 1
   * <strong>array:</strong><br />
1488
   * 1. return null
1489
   * </p><br />
1490 1
   *
1491
   * <p>
1492 1
   * <strong>object:</strong><br />
1493 1
   * 1. return false
1494 1
   * </p><br />
1495
   *
1496 1
   * <p>
1497
   * <strong>null:</strong><br />
1498
   * 1. return null
1499
   * </p>
1500 1
   *
1501
   * @param mixed $var
1502 1
   *
1503
   * @return mixed
1504
   */
1505
  public function secure($var)
1506
  {
1507
    if (
1508
        $var === ''
1509
        ||
1510
        ($this->_convert_null_to_empty_string === true && $var === null)
1511
    ) {
1512
      return "''";
1513
    }
1514
1515
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
1516
      return $var;
1517 6
    }
1518
1519
    if (is_string($var)) {
1520 6
      $var = trim(trim($var), "'");
1521
    }
1522 6
1523 1
    $var = $this->escape($var, false, false, null);
1524
1525 1
    if (is_string($var)) {
1526
      $var = "'" . trim($var, "'") . "'";
1527
    }
1528 6
1529 2
    return $var;
1530
  }
1531 2
1532
  /**
1533
   * Execute a "select"-query.
1534 6
   *
1535
   * @param string       $table
1536 6
   * @param string|array $where
1537 2
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1538 6
   *
1539 4
   * @return false|Result <p>false on error</p>
1540 4
   *
1541 1
   * @throws QueryException
1542
   */
1543 View Code Duplication
  public function select($table, $where = '1=1', $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...
1544 6
  {
1545
    // init
1546
    $table = trim($table);
1547
1548 6
    if ($table === '') {
1549
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1550 6
1551
      return false;
1552
    }
1553
1554
    if (is_string($where)) {
1555
      $WHERE = $this->escape($where, false);
1556
    } elseif (is_array($where)) {
1557
      $WHERE = $this->_parseArrayPair($where, 'AND');
1558
    } else {
1559
      $WHERE = '';
1560
    }
1561
1562
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type string|null 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...
1563
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1564 2
    }
1565
1566
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
1567 2
1568
    return $this->query($sql);
1569 2
  }
1570 1
1571
  /**
1572 1
   * Set the current charset.
1573
   *
1574
   * @param string $charset
1575 2
   *
1576 1
   * @return bool
1577 2
   */
1578 2
  public function set_charset($charset)
1579 2
  {
1580 1
    $charsetLower = strtolower($charset);
1581
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
1582
      $charset = 'utf8';
1583 2
    }
1584
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
1585
      $charset = 'utf8mb4';
1586
    }
1587 2
1588
    $this->charset = (string)$charset;
1589 2
1590
    $return = mysqli_set_charset($this->link, $charset);
1591
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1592
    /** @noinspection UsageOfSilenceOperatorInspection */
1593
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
1594
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1595
    /** @noinspection UsageOfSilenceOperatorInspection */
1596
    @\mysqli_query($this->link, "SET NAMES '" . $charset . "'");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1597
1598
    return $return;
1599
  }
1600
1601
  /**
1602
   * Set the option to convert null to "''" (empty string).
1603 20
   *
1604
   * Used in secure() => select(), insert(), update(), delete()
1605
   *
1606 20
   * @param $bool
1607
   */
1608 20
  public function set_convert_null_to_empty_string($bool)
1609 1
  {
1610
    $this->_convert_null_to_empty_string = (bool)$bool;
1611 1
  }
1612
1613
  /**
1614 20
   * Enables or disables internal report functions
1615 5
   *
1616 20
   * @link http://php.net/manual/en/function.mysqli-report.php
1617 16
   *
1618 16
   * @param int $flags <p>
1619 1
   *                   <table>
1620
   *                   Supported flags
1621
   *                   <tr valign="top">
1622 20
   *                   <td>Name</td>
1623
   *                   <td>Description</td>
1624
   *                   </tr>
1625
   *                   <tr valign="top">
1626 20
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
1627
   *                   <td>Turns reporting off</td>
1628 20
   *                   </tr>
1629
   *                   <tr valign="top">
1630
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
1631
   *                   <td>Report errors from mysqli function calls</td>
1632
   *                   </tr>
1633
   *                   <tr valign="top">
1634
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
1635
   *                   <td>
1636 1
   *                   Throw <b>mysqli_sql_exception</b> for errors
1637
   *                   instead of warnings
1638 1
   *                   </td>
1639
   *                   </tr>
1640 1
   *                   <tr valign="top">
1641
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
1642
   *                   <td>Report if no index or bad index was used in a query</td>
1643
   *                   </tr>
1644
   *                   <tr valign="top">
1645
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
1646 9
   *                   <td>Set all options (report all)</td>
1647
   *                   </tr>
1648 9
   *                   </table>
1649
   *                   </p>
1650
   *
1651
   * @return bool
1652
   */
1653
  public function set_mysqli_report($flags)
1654
  {
1655
    return \mysqli_report($flags);
1656
  }
1657
1658
  /**
1659
   * Show config errors by throw exceptions.
1660
   *
1661
   * @return bool
1662
   *
1663
   * @throws \InvalidArgumentException
1664
   */
1665
  public function showConfigError()
1666 2
  {
1667
1668 2
    if (
1669
        !$this->hostname
1670 2
        ||
1671 2
        !$this->username
1672 2
        ||
1673 2
        !$this->database
1674
    ) {
1675
1676
      if (!$this->hostname) {
1677
        throw new \InvalidArgumentException('no-sql-hostname');
1678
      }
1679
1680
      if (!$this->username) {
1681
        throw new \InvalidArgumentException('no-sql-username');
1682
      }
1683
1684
      if (!$this->database) {
1685
        throw new \InvalidArgumentException('no-sql-database');
1686
      }
1687
1688
      return false;
1689 2
    }
1690
1691 2
    return true;
1692 2
  }
1693
1694
  /**
1695
   * alias: "beginTransaction()"
1696
   */
1697
  public function startTransaction()
1698
  {
1699
    $this->beginTransaction();
1700
  }
1701
1702
  /**
1703
   * Execute a "update"-query.
1704
   *
1705
   * @param string       $table
1706
   * @param array        $data
1707
   * @param array|string $where
1708
   * @param null|string  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1709
   *
1710
   * @return false|int <p>false on error</p>
1711
   *
1712
   * @throws QueryException
1713
   */
1714
  public function update($table, array $data = array(), $where = '1=1', $databaseName = null)
1715
  {
1716
    // init
1717
    $table = trim($table);
1718
1719
    if ($table === '') {
1720
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1721
1722
      return false;
1723
    }
1724
1725
    if (count($data) === 0) {
1726
      $this->_debug->displayError('Invalid data for UPDATE, data is empty.', false);
1727
1728
      return false;
1729
    }
1730
1731
    $SET = $this->_parseArrayPair($data);
1732
1733
    if (is_string($where)) {
1734
      $WHERE = $this->escape($where, false);
1735
    } elseif (is_array($where)) {
1736
      $WHERE = $this->_parseArrayPair($where, 'AND');
1737
    } else {
1738
      $WHERE = '';
1739
    }
1740
1741
    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...
1742
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1743
    }
1744
1745
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1746
1747
    return $this->query($sql);
1748
  }
1749
1750
}
1751