Completed
Push — master ( 94ed9c...0fccc4 )
by Lars
69:46 queued 54:49
created

DB::_parseQueryParams()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.074

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 5
cts 6
cp 0.8333
rs 9.0534
c 0
b 0
f 0
cc 4
eloc 12
nc 3
nop 2
crap 4.074
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
    if (!$this->errors()) {
653 33
      $return = \mysqli_commit($this->link);
654 33
    } else {
655
      $this->rollback();
656
      $return = false;
657
    }
658 33
659
    \mysqli_autocommit($this->link, true);
660 33
    $this->_in_transaction = false;
661 33
662
    return $return;
663 33
  }
664 33
665 33
  /**
666 33
   * Get all errors from "$this->_errors".
667
   *
668 33
   * @return array|false <p>false === on errors</p>
669 33
   */
670
  public function errors()
671
  {
672
    $errors = $this->_debug->getErrors();
673 24
674
    return count($errors) > 0 ? $errors : false;
675
  }
676 33
677
  /**
678
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
679
   *
680 5
   * @param mixed     $var           boolean: convert into "integer"<br />
681
   *                                 int: int (don't change it)<br />
682
   *                                 float: float (don't change it)<br />
683 33
   *                                 null: null (don't change it)<br />
684
   *                                 array: run escape() for every key => value<br />
685
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
686
   * @param bool      $stripe_non_utf8
687 3
   * @param bool      $html_entity_decode
688 3
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
689
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
690
   *                                 <strong>null</strong> => Convert the array into null, every time.
691 1
   *
692 1
   * @return mixed
693
   */
694 1
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
695 1
  {
696
    if ($var === '') {
697
      return '';
698 1
    }
699 1
700
    if ($var === null) {
701 1
      return null;
702 1
    }
703
704 1
    // save the current value as int (for later usage)
705
    if (!is_object($var)) {
706
      $varInt = (int)$var;
707 1
    }
708
709
    /** @noinspection TypeUnsafeComparisonInspection */
710
    if (
711 33
        is_int($var)
712
        ||
713
        is_bool($var)
714 3
        ||
715 3
        (
716 3
            isset($varInt, $var[0])
717 3
            &&
718 33
            $var[0] != '0'
719
            &&
720
            "$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...
721
        )
722 33
    ) {
723
724 33
      // "int" || int || bool
725 9
726 9
      return (int)$var;
727
    }
728 33
729
    if (is_float($var)) {
730 1
731 1
      // float
732
733 33
      return $var;
734
    }
735 33
736
    if (is_array($var)) {
737 33
738
      // array
739
740
      if ($convert_array === null) {
741 3
        return null;
742
      }
743
744
      $varCleaned = array();
745
      foreach ((array)$var as $key => $value) {
746 3
747
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
748
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
749
750
        /** @noinspection OffsetOperationsInspection */
751
        $varCleaned[$key] = $value;
752 2
      }
753
754
      if ($convert_array === true) {
755
        $varCleaned = implode(',', $varCleaned);
756
757
        return $varCleaned;
758
      }
759
760
      return (array)$varCleaned;
761 35
    }
762
763 35
    if (
764
        is_string($var)
765
        ||
766
        (
767
            is_object($var)
768
            &&
769
            method_exists($var, '__toString')
770
        )
771 22
    ) {
772
773 22
      // "string"
774
775
      $var = (string)$var;
776
777
      if ($stripe_non_utf8 === true) {
778
        $var = UTF8::cleanup($var);
779
      }
780
781 8
      if ($html_entity_decode === true) {
782
        // use no-html-entity for db
783 8
        $var = UTF8::html_entity_decode($var);
784
      }
785
786
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
787
788
      $var = \mysqli_real_escape_string($this->getLink(), $var);
789
790
      return (string)$var;
791
792
    }
793
794
    if ($var instanceof \DateTime) {
795
796
      // "DateTime"-object
797
798 9
      try {
799
        return $this->escape($var->format('Y-m-d H:i:s'), false);
800 9
      } catch (\Exception $e) {
801 1
        return null;
802
      }
803
804 1
    } else {
805
      return false;
806
    }
807
  }
808
809 1
  /**
810
   * Execute select/insert/update/delete sql-queries.
811
   *
812 1
   * @param string $query    sql-query
813 1
   * @param bool   $useCache use cache?
814
   * @param int    $cacheTTL cache-ttl in seconds
815
   *
816 1
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
817
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
818
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
819 8
   *               "true" by e.g. "DROP"-queries<br />
820
   *               "false" on error
821
   *
822 8
   * @throws QueryException
823
   */
824 8
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
825
  {
826
    // init
827
    $cacheKey = null;
828
    $db = self::getInstance();
829
830 View Code Duplication
    if ($useCache === true) {
831
      $cache = new Cache(null, null, false, $useCache);
832
      $cacheKey = 'sql-' . md5($query);
833
834 3
      if (
835
          $cache->getCacheIsReady() === true
836 3
          &&
837
          $cache->existsItem($cacheKey)
838 3
      ) {
839 2
        return $cache->getItem($cacheKey);
840 2
      }
841
842 3
    } else {
843 3
      $cache = false;
844 3
    }
845 3
846
    $result = $db->query($query);
847 3
848
    if ($result instanceof Result) {
849
850
      $return = $result->fetchAllArray();
851
852
      // save into the cache
853 View Code Duplication
      if (
854
          $cacheKey !== null
855
          &&
856 3
          $useCache === true
857
          &&
858
          $cache instanceof Cache
859 3
          &&
860 3
          $cache->getCacheIsReady() === true
861 3
      ) {
862 3
        $cache->setItem($cacheKey, $return, $cacheTTL);
863
      }
864
865 3
    } else {
866
      $return = $result;
867
    }
868
869
    return $return;
870
  }
871
872
  /**
873
   * Get all table-names via "SHOW TABLES".
874
   *
875
   * @return array
876
   */
877
  public function getAllTables()
878
  {
879
    $query = 'SHOW TABLES';
880
    $result = $this->query($query);
881
882
    return $result->fetchAllArray();
883
  }
884
885
  /**
886 3
   * @return Debug
887
   */
888
  public function getDebugger()
889 3
  {
890 3
    return $this->_debug;
891
  }
892 3
893 1
  /**
894 1
   * Get errors from "$this->_errors".
895
   *
896
   * @return array
897 1
   */
898 1
  public function getErrors()
899 1
  {
900 1
    return $this->_debug->getErrors();
901 1
  }
902
903
  /**
904 1
   * getInstance()
905 3
   *
906
   * @param string      $hostname
907
   * @param string      $username
908 3
   * @param string      $password
909
   * @param string      $database
910 3
   * @param string      $port          default is (int)3306
911
   * @param string      $charset       default is 'utf8' or 'utf8mb4' (if supported)
912 1
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
913
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
914
   * @param string      $logger_class_name
915
   * @param string      $logger_level
916
   * @param bool|string $session_to_db use a empty string "" or false to disable it
917 1
   *
918
   * @return \voku\db\DB
919 1
   */
920
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
921 1
  {
922 1
    /**
923 1
     * @var $instance DB[]
924 1
     */
925 1
    static $instance = array();
926
927 1
    /**
928 2
     * @var $firstInstance DB
929
     */
930
    static $firstInstance = null;
931 3
932
    if (
933
        $hostname . $username . $password . $database . $port . $charset == ''
934
        &&
935
        null !== $firstInstance
936
    ) {
937
      return $firstInstance;
938
    }
939 1
940
    $connection = md5(
941 1
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
942
    );
943
944
    if (!isset($instance[$connection])) {
945
      $instance[$connection] = new self(
946
          $hostname,
947
          $username,
948
          $password,
949
          $database,
950
          $port,
951
          $charset,
952
          $exit_on_error,
953
          $echo_on_error,
954
          $logger_class_name,
955
          $logger_level,
956
          $session_to_db
957
      );
958
959
      if (null === $firstInstance) {
960
        $firstInstance = $instance[$connection];
961
      }
962
    }
963
964
    return $instance[$connection];
965
  }
966
967
  /**
968
   * Get the mysqli-link (link identifier returned by mysqli-connect).
969
   *
970
   * @return \mysqli
971
   */
972
  public function getLink()
973
  {
974
    return $this->link;
975
  }
976
977
  /**
978
   * Get the current charset.
979
   *
980
   * @return string
981
   */
982
  public function get_charset()
983
  {
984
    return $this->charset;
985
  }
986
987
  /**
988
   * Check if we are in a transaction.
989
   *
990
   * @return bool
991
   */
992
  public function inTransaction()
993
  {
994
    return $this->_in_transaction;
995
  }
996 7
997
  /**
998 7
   * Execute a "insert"-query.
999 7
   *
1000 5
   * @param string      $table
1001 5
   * @param array       $data
1002 7
   * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1003 5
   *
1004 5
   * @return false|int <p>false on error</p>
1005
   *
1006 7
   * @throws QueryException
1007
   */
1008 7
  public function insert($table, array $data = array(), $databaseName = null)
1009
  {
1010
    // init
1011 7
    $table = trim($table);
1012
1013
    if ($table === '') {
1014 7
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1015
1016 7
      return false;
1017
    }
1018
1019
    if (count($data) === 0) {
1020
      $this->_debug->displayError('Invalid data for INSERT, data is empty.', false);
1021
1022
      return false;
1023
    }
1024
1025
    $SET = $this->_parseArrayPair($data);
1026 1
1027
    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...
1028 1
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1029 1
    }
1030
1031
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET;";
1032
1033
    return $this->query($sql);
1034
  }
1035
1036 1
  /**
1037
   * Returns the auto generated id used in the last query.
1038 1
   *
1039 1
   * @return int|string
1040
   */
1041 1
  public function insert_id()
1042
  {
1043
    return \mysqli_insert_id($this->link);
1044
  }
1045
1046
  /**
1047
   * Check if db-connection is ready.
1048
   *
1049
   * @return boolean
1050
   */
1051
  public function isReady()
1052
  {
1053
    return $this->connected ? true : false;
1054
  }
1055
1056 1
  /**
1057
   * Get the last sql-error.
1058 1
   *
1059
   * @return string|false <p>false === there was no error</p>
1060
   */
1061
  public function lastError()
1062 1
  {
1063 1
    $errors = $this->_debug->getErrors();
1064
1065 1
    return count($errors) > 0 ? end($errors) : false;
1066
  }
1067
1068 1
  /**
1069 1
   * Execute a sql-multi-query.
1070 1
   *
1071
   * @param string $sql
1072 1
   *
1073
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1074 1
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1075 1
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1076 1
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1077
   *
1078 1
   * @throws QueryException
1079
   */
1080 1
  public function multi_query($sql)
1081 1
  {
1082 1
    if (!$this->isReady()) {
1083 1
      return false;
1084 1
    }
1085
1086 View Code Duplication
    if (!$sql || $sql === '') {
1087 1
      $this->_debug->displayError('Can not execute an empty query.', false);
1088 1
1089 1
      return false;
1090
    }
1091
1092
    $query_start_time = microtime(true);
1093 1
    $resultTmp = \mysqli_multi_query($this->link, $sql);
1094
    $query_duration = microtime(true) - $query_start_time;
1095 1
1096
    $this->_debug->logQuery($sql, $query_duration, 0);
1097
1098
    $returnTheResult = false;
1099
    $result = array();
1100
    if ($resultTmp) {
1101
      do {
1102
        $resultTmpInner = \mysqli_store_result($this->link);
1103
1104
        if ($resultTmpInner instanceof \mysqli_result) {
1105
          $returnTheResult = true;
1106
          $result[] = new Result($sql, $resultTmpInner);
1107
        } else {
1108 1
          $errorMsg = \mysqli_error($this->link);
1109 1
1110
          // is the query successful
1111
          if ($resultTmpInner === true || !$errorMsg) {
1112 1
            $result[] = true;
1113 1
          } else {
1114
            $result[] = $this->queryErrorHandling($errorMsg, $sql);
1115
          }
1116
        }
1117
      } while (\mysqli_more_results($this->link) === true ? \mysqli_next_result($this->link) : false);
1118
1119
    } else {
1120
1121
      $errorMsg = \mysqli_error($this->link);
1122 1
1123
      if ($this->_debug->checkForDev() === true) {
1124 1
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1125 1
        echo 'Error:' . $errorMsg;
1126
      }
1127
1128
      $this->_debug->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1129
    }
1130
1131
    // return the result only if there was a "SELECT"-query
1132 4
    if ($returnTheResult === true) {
1133
      return $result;
1134 4
    }
1135
1136 4
    if (in_array(false, $result, true) === false) {
1137 1
      return true;
1138
    }
1139 1
1140
    return false;
1141
  }
1142 4
1143
  /**
1144
   * Pings a server connection, or tries to reconnect
1145
   * if the connection has gone down.
1146
   *
1147
   * @return boolean
1148 4
   */
1149 4
  public function ping()
1150
  {
1151 4
    if (
1152
        $this->link
1153
        &&
1154
        $this->link instanceof \mysqli
1155
    ) {
1156
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1157
      /** @noinspection UsageOfSilenceOperatorInspection */
1158
      return (bool)@\mysqli_ping($this->link);
1159 4
    }
1160
1161 4
    return false;
1162
  }
1163
1164
  /**
1165
   * Selects a different database than the one specified on construction.
1166
   *
1167
   * @param string $database <p>Database name to switch to.</p>
1168
   *
1169 4
   * @return bool <p>Boolean true on success, false otherwise.</p>
1170
   */
1171 4
  public function select_db($database)
1172
  {
1173
    if (!$this->isReady()) {
1174
      return false;
1175
    }
1176
1177
    return mysqli_select_db($this->link, $database);
1178
  }
1179 2
1180
  /**
1181
   * Get a new "Prepare"-Object for your sql-query.
1182 2
   *
1183 1
   * @param string $query
1184 1
   *
1185 1
   * @return Prepare
1186 1
   */
1187 1
  public function prepare($query)
1188
  {
1189
    return new Prepare($this, $query);
1190 2
  }
1191 2
1192
  /**
1193 2
   * Execute a sql-query and return the result-array for select-statements.
1194
   *
1195
   * @param $query
1196
   *
1197
   * @return mixed
1198
   * @deprecated
1199
   * @throws \Exception
1200
   */
1201 2
  public static function qry($query)
1202
  {
1203 2
    $db = self::getInstance();
1204
1205 2
    $args = func_get_args();
1206
    /** @noinspection SuspiciousAssignmentsInspection */
1207
    $query = array_shift($args);
1208
    $query = str_replace('?', '%s', $query);
1209
    $args = array_map(
1210
        array(
1211 2
            $db,
1212
            'escape',
1213
        ),
1214 2
        $args
1215
    );
1216 2
    array_unshift($args, $query);
1217 2
    $query = call_user_func_array('sprintf', $args);
1218 2
    $result = $db->query($query);
1219 2
1220 2
    if ($result instanceof Result) {
1221
      return $result->fetchAllArray();
1222 2
    }
1223
1224
    return $result;
1225
  }
1226
1227
  /**
1228
   * Execute a sql-query.
1229
   *
1230
   * @param string        $sql            <p>The sql query-string.</p>
1231
   *
1232
   * @param array|boolean $params         <p>
1233
   *                                      "array" of sql-query-parameters<br/>
1234
   *                                      "false" if you don't need any parameter (default)<br/>
1235
   *                                      </p>
1236 21
   *
1237
   * @return bool|int|Result              <p>
1238
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
1239 21
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
1240
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1241 21
   *                                      "true" by e.g. "DROP"-queries<br />
1242 2
   *                                      "false" on error
1243
   *                                      </p>
1244 2
   *
1245
   * @throws QueryException
1246
   */
1247 20
  public function query($sql = '', $params = false)
1248 3
  {
1249
    if (!$this->isReady()) {
1250 3
      return false;
1251
    }
1252
1253 18 View Code Duplication
    if (!$sql || $sql === '') {
1254
      $this->_debug->displayError('Can not execute an empty query.', false);
1255 18
1256
      return false;
1257
    }
1258
1259 18
    if (
1260
        $params !== false
1261 18
        &&
1262
        is_array($params)
1263
        &&
1264
        count($params) > 0
1265
    ) {
1266
      $sql = $this->_parseQueryParams($sql, $params);
1267
    }
1268
1269
    $query_start_time = microtime(true);
1270
    $query_result = \mysqli_real_query($this->link, $sql);
1271
    $query_duration = microtime(true) - $query_start_time;
1272
1273
    $this->query_count++;
1274 23
1275
    $mysqli_field_count = \mysqli_field_count($this->link);
1276
    if ($mysqli_field_count) {
1277 23
      $result = \mysqli_store_result($this->link);
1278
    } else {
1279
      $result = $query_result;
1280 23
    }
1281
1282
    if ($result instanceof \mysqli_result) {
1283
1284 23
      // log the select query
1285 23
      $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 1266 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...
1286 23
1287 23
      // return query result object
1288 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 1266 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...
1289
1290 23
    }
1291 2
1292 2
    if ($query_result === true) {
1293
1294 23
      // "INSERT" || "REPLACE"
1295 1 View Code Duplication
      if (preg_match('/^\s*"?(INSERT|REPLACE)\s+/i', $sql)) {
1296 1
        $insert_id = (int)$this->insert_id();
1297
        $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 1266 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 $insert_id;
1300 1
      }
1301
1302 23
      // "UPDATE" || "DELETE"
1303 1 View Code Duplication
      if (preg_match('/^\s*"?(UPDATE|DELETE)\s+/i', $sql)) {
1304 1
        $affected_rows = (int)$this->affected_rows();
1305
        $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 1266 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...
1306 23
1307 1
        return $affected_rows;
1308 1
      }
1309
1310 23
      // log the ? query
1311 1
      $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 1266 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...
1312 1
1313
      return true;
1314 23
    }
1315 1
1316 1
    // log the error query
1317
    $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 1266 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...
1318 23
1319 2
    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 1266 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...
1320 2
  }
1321
1322 23
  /**
1323 2
   * Error-handling for the sql-query.
1324 2
   *
1325
   * @param string     $errorMsg
1326 23
   * @param string     $sql
1327 4
   * @param array|bool $sqlParams false if there wasn't any parameter
1328 4
   *
1329
   * @throws QueryException
1330 23
   * @throws DBGoneAwayException
1331 1
   *
1332 1
   * @return bool
1333
   */
1334 23 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...
1335 4
  {
1336 4
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
1337
      static $RECONNECT_COUNTER;
1338 23
1339 1
      // exit if we have more then 3 "DB server has gone away"-errors
1340 1
      if ($RECONNECT_COUNTER > 3) {
1341
        $this->_debug->mailToAdmin('DB-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
1342 23
        throw new DBGoneAwayException($errorMsg);
1343 1
      }
1344 1
1345
      $this->_debug->mailToAdmin('DB-Error', $errorMsg . ":\n<br />" . $sql);
1346 23
1347 2
      // reconnect
1348 2
      $RECONNECT_COUNTER++;
1349
      $this->reconnect(true);
1350 23
1351 1
      // re-run the current query
1352 1
      return $this->query($sql, $sqlParams);
1353
    }
1354 23
1355 2
    $this->_debug->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
1356 2
1357 2
    // this query returned an error, we must display it (only for dev) !!!
1358
    $this->_debug->displayError($errorMsg . ' | ' . $sql);
1359 2
1360 1
    return false;
1361 2
  }
1362 1
1363 1
  /**
1364
   * Quote && Escape e.g. a table name string.
1365 2
   *
1366 23
   * @param string $str
1367
   *
1368
   * @return string
1369 23
   */
1370 23
  public function quote_string($str)
1371 23
  {
1372
    $str = str_replace(
1373 23
        '`',
1374 23
        '``',
1375 23
        trim(
1376 23
            $this->escape($str, false),
1377
            '`'
1378 23
        )
1379 23
    );
1380 23
1381
    return '`' . $str . '`';
1382 23
  }
1383 23
1384 23
  /**
1385
   * Reconnect to the MySQL-Server.
1386 23
   *
1387 23
   * @param bool $checkViaPing
1388 23
   *
1389
   * @return bool
1390 23
   */
1391 23
  public function reconnect($checkViaPing = false)
1392
  {
1393 23
    $ping = false;
1394
1395 23
    if ($checkViaPing === true) {
1396
      $ping = $this->ping();
1397 23
    }
1398 1
1399 23
    if ($ping !== true) {
1400 23
      $this->connected = false;
1401 23
      $this->connect();
1402
    }
1403 23
1404 1
    return $this->isReady();
1405 1
  }
1406
1407 23
  /**
1408 23
   * Execute a "replace"-query.
1409 23
   *
1410
   * @param string      $table
1411 23
   * @param array       $data
1412 2
   * @param null|string $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1413 2
   *
1414
   * @return false|int <p>false on error</p>
1415 23
   *
1416 23
   * @throws QueryException
1417
   */
1418 23
  public function replace($table, array $data = array(), $databaseName = null)
1419
  {
1420
    // init
1421
    $table = trim($table);
1422
1423
    if ($table === '') {
1424
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1425
1426
      return false;
1427
    }
1428 26
1429
    if (count($data) === 0) {
1430 26
      $this->_debug->displayError('Invalid data for REPLACE, data is empty.', false);
1431 26
1432 26
      return false;
1433 26
    }
1434 26
1435
    // extracting column names
1436 26
    $columns = array_keys($data);
1437 26
    foreach ($columns as $k => $_key) {
1438
      /** @noinspection AlterInForeachInspection */
1439 26
      $columns[$k] = $this->quote_string($_key);
1440
    }
1441
1442
    $columns = implode(',', $columns);
1443
1444
    // extracting values
1445
    foreach ($data as $k => $_value) {
1446
      /** @noinspection AlterInForeachInspection */
1447 1
      $data[$k] = $this->secure($_value);
1448
    }
1449 1
    $values = implode(',', $data);
1450
1451
    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...
1452
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1453
    }
1454
1455
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values);";
1456
1457
    return $this->query($sql);
1458
  }
1459
1460
  /**
1461
   * Rollback in a transaction and end the transaction.
1462
   *
1463 1
   * @return bool <p>Boolean true on success, false otherwise.</p>
1464
   */
1465 View Code Duplication
  public function rollback()
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...
1466 1
  {
1467
    if ($this->_in_transaction === false) {
1468 1
      return false;
1469 1
    }
1470
1471 1
    $return = \mysqli_rollback($this->link);
1472
    \mysqli_autocommit($this->link, true);
1473
    $this->_in_transaction = false;
1474 1
1475 1
    return $return;
1476
  }
1477 1
1478
1479
  /**
1480
   * Commits the current transaction and end the transaction.
1481 1
   *
1482 1
   * @return bool <p>Boolean true on success, false otherwise.</p>
1483
   */
1484 1 View Code Duplication
  public function commit()
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...
1485 1
  {
1486
    if ($this->_in_transaction === false) {
1487 1
      return false;
1488
    }
1489
1490 1
    $return = mysqli_commit($this->link);
1491
    \mysqli_autocommit($this->link, true);
1492 1
    $this->_in_transaction = false;
1493 1
1494 1
    return $return;
1495
  }
1496 1
1497
  /**
1498
   * Try to secure a variable, so can you use it in sql-queries.
1499
   *
1500 1
   * <p>
1501
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
1502 1
   * 1. parse into "int"
1503
   * </p><br />
1504
   *
1505
   * <p>
1506
   * <strong>float:</strong><br />
1507
   * 1. return "float"
1508
   * </p><br />
1509
   *
1510
   * <p>
1511
   * <strong>string:</strong><br />
1512
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
1513
   * 2. trim whitespace<br />
1514
   * 3. trim '<br />
1515
   * 4. escape the string (and remove non utf-8 chars)<br />
1516
   * 5. trim ' again (because we maybe removed some chars)<br />
1517 6
   * 6. add ' around the new string<br />
1518
   * </p><br />
1519
   *
1520 6
   * <p>
1521
   * <strong>array:</strong><br />
1522 6
   * 1. return null
1523 1
   * </p><br />
1524
   *
1525 1
   * <p>
1526
   * <strong>object:</strong><br />
1527
   * 1. return false
1528 6
   * </p><br />
1529 2
   *
1530
   * <p>
1531 2
   * <strong>null:</strong><br />
1532
   * 1. return null
1533
   * </p>
1534 6
   *
1535
   * @param mixed $var
1536 6
   *
1537 2
   * @return mixed
1538 6
   */
1539 4
  public function secure($var)
1540 4
  {
1541 1
    if (
1542
        $var === ''
1543
        ||
1544 6
        ($this->_convert_null_to_empty_string === true && $var === null)
1545
    ) {
1546
      return "''";
1547
    }
1548 6
1549
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
1550 6
      return $var;
1551
    }
1552
1553
    if (is_string($var)) {
1554
      $var = trim(trim($var), "'");
1555
    }
1556
1557
    $var = $this->escape($var, false, false, null);
1558
1559
    if (is_string($var)) {
1560
      $var = "'" . trim($var, "'") . "'";
1561
    }
1562
1563
    return $var;
1564 2
  }
1565
1566
  /**
1567 2
   * Execute a "select"-query.
1568
   *
1569 2
   * @param string       $table
1570 1
   * @param string|array $where
1571
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1572 1
   *
1573
   * @return false|Result <p>false on error</p>
1574
   *
1575 2
   * @throws QueryException
1576 1
   */
1577 2 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...
1578 2
  {
1579 2
    // init
1580 1
    $table = trim($table);
1581
1582
    if ($table === '') {
1583 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1584
1585
      return false;
1586
    }
1587 2
1588
    if (is_string($where)) {
1589 2
      $WHERE = $this->escape($where, false);
1590
    } elseif (is_array($where)) {
1591
      $WHERE = $this->_parseArrayPair($where, 'AND');
1592
    } else {
1593
      $WHERE = '';
1594
    }
1595
1596
    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...
1597
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1598
    }
1599
1600
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
1601
1602
    return $this->query($sql);
1603 20
  }
1604
1605
  /**
1606 20
   * Set the current charset.
1607
   *
1608 20
   * @param string $charset
1609 1
   *
1610
   * @return bool
1611 1
   */
1612
  public function set_charset($charset)
1613
  {
1614 20
    $charsetLower = strtolower($charset);
1615 5
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
1616 20
      $charset = 'utf8';
1617 16
    }
1618 16
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
1619 1
      $charset = 'utf8mb4';
1620
    }
1621
1622 20
    $this->charset = (string)$charset;
1623
1624
    $return = mysqli_set_charset($this->link, $charset);
1625
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1626 20
    /** @noinspection UsageOfSilenceOperatorInspection */
1627
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
1628 20
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1629
    /** @noinspection UsageOfSilenceOperatorInspection */
1630
    @\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...
1631
1632
    return $return;
1633
  }
1634
1635
  /**
1636 1
   * Set the option to convert null to "''" (empty string).
1637
   *
1638 1
   * Used in secure() => select(), insert(), update(), delete()
1639
   *
1640 1
   * @param $bool
1641
   */
1642
  public function set_convert_null_to_empty_string($bool)
1643
  {
1644
    $this->_convert_null_to_empty_string = (bool)$bool;
1645
  }
1646 9
1647
  /**
1648 9
   * Enables or disables internal report functions
1649
   *
1650
   * @link http://php.net/manual/en/function.mysqli-report.php
1651
   *
1652
   * @param int $flags <p>
1653
   *                   <table>
1654
   *                   Supported flags
1655
   *                   <tr valign="top">
1656
   *                   <td>Name</td>
1657
   *                   <td>Description</td>
1658
   *                   </tr>
1659
   *                   <tr valign="top">
1660
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
1661
   *                   <td>Turns reporting off</td>
1662
   *                   </tr>
1663
   *                   <tr valign="top">
1664
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
1665
   *                   <td>Report errors from mysqli function calls</td>
1666 2
   *                   </tr>
1667
   *                   <tr valign="top">
1668 2
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
1669
   *                   <td>
1670 2
   *                   Throw <b>mysqli_sql_exception</b> for errors
1671 2
   *                   instead of warnings
1672 2
   *                   </td>
1673 2
   *                   </tr>
1674
   *                   <tr valign="top">
1675
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
1676
   *                   <td>Report if no index or bad index was used in a query</td>
1677
   *                   </tr>
1678
   *                   <tr valign="top">
1679
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
1680
   *                   <td>Set all options (report all)</td>
1681
   *                   </tr>
1682
   *                   </table>
1683
   *                   </p>
1684
   *
1685
   * @return bool
1686
   */
1687
  public function set_mysqli_report($flags)
1688
  {
1689 2
    return \mysqli_report($flags);
1690
  }
1691 2
1692 2
  /**
1693
   * Show config errors by throw exceptions.
1694
   *
1695
   * @return bool
1696
   *
1697
   * @throws \InvalidArgumentException
1698
   */
1699
  public function showConfigError()
1700
  {
1701
1702
    if (
1703
        !$this->hostname
1704
        ||
1705
        !$this->username
1706
        ||
1707
        !$this->database
1708
    ) {
1709
1710
      if (!$this->hostname) {
1711
        throw new \InvalidArgumentException('no-sql-hostname');
1712
      }
1713
1714
      if (!$this->username) {
1715
        throw new \InvalidArgumentException('no-sql-username');
1716
      }
1717
1718
      if (!$this->database) {
1719
        throw new \InvalidArgumentException('no-sql-database');
1720
      }
1721
1722
      return false;
1723
    }
1724
1725
    return true;
1726
  }
1727
1728
  /**
1729
   * alias: "beginTransaction()"
1730
   */
1731
  public function startTransaction()
1732
  {
1733
    $this->beginTransaction();
1734
  }
1735
1736
  /**
1737
   * Execute a "update"-query.
1738
   *
1739
   * @param string       $table
1740
   * @param array        $data
1741
   * @param array|string $where
1742
   * @param null|string  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1743
   *
1744
   * @return false|int <p>false on error</p>
1745
   *
1746
   * @throws QueryException
1747
   */
1748
  public function update($table, array $data = array(), $where = '1=1', $databaseName = null)
1749
  {
1750
    // init
1751
    $table = trim($table);
1752
1753
    if ($table === '') {
1754
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1755
1756
      return false;
1757
    }
1758
1759
    if (count($data) === 0) {
1760
      $this->_debug->displayError('Invalid data for UPDATE, data is empty.', false);
1761
1762
      return false;
1763
    }
1764
1765
    $SET = $this->_parseArrayPair($data);
1766
1767
    if (is_string($where)) {
1768
      $WHERE = $this->escape($where, false);
1769
    } elseif (is_array($where)) {
1770
      $WHERE = $this->_parseArrayPair($where, 'AND');
1771
    } else {
1772
      $WHERE = '';
1773
    }
1774
1775
    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...
1776
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1777
    }
1778
1779
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1780
1781
    return $this->query($sql);
1782
  }
1783
1784
}
1785