Completed
Push — master ( a637a0...44fe92 )
by Lars
04:29 queued 01:28
created

DB::clearErrors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 4
Bugs 2 Features 2
Metric Value
c 4
b 2
f 2
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace voku\db;
4
5
use voku\cache\Cache;
6
use voku\helper\UTF8;
7
8
/**
9
 * DB: this handles DB queries via MySQLi
10
 *
11
 * @package   voku\db
12
 */
13
final class DB
14
{
15
16
  /**
17
   * @var int
18
   */
19
  public $query_count = 0;
20
21
  /**
22
   * @var \mysqli
23
   */
24
  private $link = false;
25
26
  /**
27
   * @var bool
28
   */
29
  private $connected = false;
30
31
  /**
32
   * @var array
33
   */
34
  private $mysqlDefaultTimeFunctions;
35
36
  /**
37
   * @var string
38
   */
39
  private $hostname = '';
40
41
  /**
42
   * @var string
43
   */
44
  private $username = '';
45
46
  /**
47
   * @var string
48
   */
49
  private $password = '';
50
51
  /**
52
   * @var string
53
   */
54
  private $database = '';
55
56
  /**
57
   * @var int
58
   */
59
  private $port = 3306;
60
61
  /**
62
   * @var string
63
   */
64
  private $charset = 'utf8';
65
66
  /**
67
   * @var string
68
   */
69
  private $socket = '';
70
71
  /**
72
   * @var bool
73
   */
74
  private $session_to_db = false;
75
76
  /**
77
   * @var bool
78
   */
79
  private $_in_transaction = false;
80
81
  /**
82
   * @var Debug
83
   */
84
  private $_debug;
85
86
  /**
87
   * __construct()
88
   *
89
   * @param string         $hostname
90
   * @param string         $username
91
   * @param string         $password
92
   * @param string         $database
93
   * @param int            $port
94
   * @param string         $charset
95
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
96
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
97
   * @param string         $logger_class_name
98
   * @param string         $logger_level  'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
99
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
100
   */
101 10
  protected function __construct($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
102
  {
103 10
    $this->connected = false;
104
105 10
    $this->_debug = new Debug($this);
106
107 10
    $this->_loadConfig(
108 10
        $hostname,
109 10
        $username,
110 10
        $password,
111 10
        $database,
112 10
        $port,
113 10
        $charset,
114 10
        $exit_on_error,
115 10
        $echo_on_error,
116 10
        $logger_class_name,
117 10
        $logger_level,
118
        $session_to_db
119 10
    );
120
121 7
    $this->connect();
122
123 4
    $this->mysqlDefaultTimeFunctions = array(
124
      // Returns the current date.
125 4
      'CURDATE()',
126
      // CURRENT_DATE	| Synonyms for CURDATE()
127 4
      'CURRENT_DATE()',
128
      // CURRENT_TIME	| Synonyms for CURTIME()
129 4
      'CURRENT_TIME()',
130
      // CURRENT_TIMESTAMP | Synonyms for NOW()
131 4
      'CURRENT_TIMESTAMP()',
132
      // Returns the current time.
133 4
      'CURTIME()',
134
      // Synonym for NOW()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
135 4
      'LOCALTIME()',
136
      // Synonym for NOW()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
137 4
      'LOCALTIMESTAMP()',
138
      // Returns the current date and time.
139 4
      'NOW()',
140
      // Returns the time at which the function executes.
141 4
      'SYSDATE()',
142
      // Returns a UNIX timestamp.
143 4
      'UNIX_TIMESTAMP()',
144
      // Returns the current UTC date.
145 4
      'UTC_DATE()',
146
      // Returns the current UTC time.
147 4
      'UTC_TIME()',
148
      // Returns the current UTC date and time.
149 4
      'UTC_TIMESTAMP()',
150
    );
151 4
  }
152
153
  /**
154
   * Load the config from the constructor.
155
   *
156
   * @param string         $hostname
157
   * @param string         $username
158
   * @param string         $password
159
   * @param string         $database
160
   * @param int            $port
161
   * @param string         $charset
162
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
163
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
164
   * @param string         $logger_class_name
165
   * @param string         $logger_level
166
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
167
   *
168
   * @return bool
169
   */
170 10
  private function _loadConfig($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
171
  {
172 10
    $this->hostname = (string)$hostname;
173 10
    $this->username = (string)$username;
174 10
    $this->password = (string)$password;
175 10
    $this->database = (string)$database;
176
177 10
    if ($charset) {
178 4
      $this->charset = (string)$charset;
179 4
    }
180
181 10
    if ($port) {
182 4
      $this->port = (int)$port;
183 4
    } else {
184
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
185 7
      $this->port = @ini_get('mysqli.default_port');
186
    }
187
188 10
    if (!$this->socket) {
189
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
190 10
      $this->socket = @ini_get('mysqli.default_socket');
191 10
    }
192
193 10
    if ($exit_on_error === true || $exit_on_error === false) {
194 10
      $this->_debug->setExitOnError($exit_on_error);
195 10
    }
196
197 10
    if ($echo_on_error === true || $echo_on_error === false) {
198 10
      $this->_debug->setEchoOnError($echo_on_error);
199 10
    }
200
201 10
    $this->_debug->setLoggerClassName($logger_class_name);
202 10
    $this->_debug->setLoggerLevel($logger_level);
203
204 10
    $this->session_to_db = (boolean)$session_to_db;
205
206 10
    return $this->showConfigError();
207
  }
208
209
  /**
210
   * Show config errors by throw exceptions.
211
   *
212
   * @return bool
213
   *
214
   * @throws \Exception
215
   */
216 10
  public function showConfigError()
217
  {
218
219
    if (
220 10
        !$this->hostname
221 10
        ||
222 9
        !$this->username
223 9
        ||
224 8
        !$this->database
225 10
    ) {
226
227 3
      if (!$this->hostname) {
228 1
        throw new \Exception('no-sql-hostname');
229
      }
230
231 2
      if (!$this->username) {
232 1
        throw new \Exception('no-sql-username');
233
      }
234
235 1
      if (!$this->database) {
236 1
        throw new \Exception('no-sql-database');
237
      }
238
239
      return false;
240
    }
241
242 7
    return true;
243
  }
244
245
  /**
246
   * Open a new connection to the MySQL server.
247
   *
248
   * @return boolean
249
   */
250 9
  public function connect()
251
  {
252 9
    if ($this->isReady()) {
253 1
      return true;
254
    }
255
256 9
    mysqli_report(MYSQLI_REPORT_STRICT);
257
    try {
258 9
      $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...
259
260 9
      if (Helper::isMysqlndIsUsed() === true) {
261 9
        mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
262 9
      }
263
264
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
265 9
      $this->connected = @mysqli_real_connect(
266 9
          $this->link,
267 9
          $this->hostname,
268 9
          $this->username,
269 9
          $this->password,
270 9
          $this->database,
271 9
          $this->port,
272 9
          $this->socket
273 9
      );
274 9
    } catch (\Exception $e) {
275 3
      $this->_debug->displayError('Error connecting to mysql server: ' . $e->getMessage(), true);
276
    }
277 6
    mysqli_report(MYSQLI_REPORT_OFF);
278
279 6
    if (!$this->connected) {
280
      $this->_debug->displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
281
    } else {
282 6
      $this->set_charset($this->charset);
283
    }
284
285 6
    return $this->isReady();
286
  }
287
288
  /**
289
   * Check if db-connection is ready.
290
   *
291
   * @return boolean
292
   */
293 39
  public function isReady()
294
  {
295 39
    return $this->connected ? true : false;
296
  }
297
298
  /**
299
   * Get a new "Prepare"-Object for your sql-query.
300
   *
301
   * @param string $query
302
   *
303
   * @return Prepare
304
   */
305
  public function prepare($query)
306
  {
307
    return new Prepare($this, $query);
308
  }
309
310
  /**
311
   * Execute a sql-query and return the result-array for select-statements.
312
   *
313
   * @param $query
314
   *
315
   * @return mixed
316
   * @deprecated
317
   * @throws \Exception
318
   */
319
  public static function qry($query)
320
  {
321
    $db = self::getInstance();
322
323
    $args = func_get_args();
324
    $query = array_shift($args);
325
    $query = str_replace('?', '%s', $query);
326
    $args = array_map(
327
        array(
328
            $db,
329
            'escape',
330
        ),
331
        $args
332
    );
333
    array_unshift($args, $query);
334
    $query = call_user_func_array('sprintf', $args);
335
    $result = $db->query($query);
336
337
    if ($result instanceof Result) {
338
      return $result->fetchAllArray();
339
    } else {
340
      return $result;
341
    }
342
  }
343
344
  /**
345
   * getInstance()
346
   *
347
   * @param string      $hostname
348
   * @param string      $username
349
   * @param string      $password
350
   * @param string      $database
351
   * @param string      $port          default is (int)3306
352
   * @param string      $charset       default is 'utf8', but if you need 4-byte chars, then your tables need
353
   *                                   the 'utf8mb4'-charset
354
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
355
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
356
   * @param string      $logger_class_name
357
   * @param string      $logger_level
358
   * @param bool|string $session_to_db use a empty string "" or false to disable it
359
   *
360
   * @return \voku\db\DB
361
   */
362 50
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
363
  {
364
    /**
365
     * @var $instance DB[]
366
     */
367 50
    static $instance = array();
368
369
    /**
370
     * @var $firstInstance DB
371
     */
372 50
    static $firstInstance = null;
373
374
    if (
375 50
        $hostname . $username . $password . $database . $port . $charset == ''
376 50
        &&
377 8
        null !== $firstInstance
378 50
    ) {
379 8
      return $firstInstance;
380
    }
381
382 50
    $connection = md5(
383 50
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
384 50
    );
385
386 50
    if (!isset($instance[$connection])) {
387 10
      $instance[$connection] = new self(
388 10
          $hostname,
389 10
          $username,
390 10
          $password,
391 10
          $database,
392 10
          $port,
393 10
          $charset,
394 10
          $exit_on_error,
395 10
          $echo_on_error,
396 10
          $logger_class_name,
397 10
          $logger_level,
398
          $session_to_db
399 10
      );
400
401 4
      if (null === $firstInstance) {
402 1
        $firstInstance = $instance[$connection];
403 1
      }
404 4
    }
405
406 50
    return $instance[$connection];
407
  }
408
409
  /**
410
   * Execute a sql-query.
411
   *
412
   * @param string        $sql            sql-query
413
   *
414
   * @param array|boolean $params         "array" of sql-query-parameters
415
   *                                      "false" if you don't need any parameter (default)
416
   *
417
   * @return bool|int|Result              "Result" by "<b>SELECT</b>"-queries<br />
418
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
419
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
420
   *                                      "true" by e.g. "DROP"-queries<br />
421
   *                                      "false" on error
422
   *
423
   * @throws \Exception
424
   */
425 29
  public function query($sql = '', $params = false)
426
  {
427 29
    if (!$this->isReady()) {
428
      return false;
429
    }
430
431 29 View Code Duplication
    if (!$sql || $sql === '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
432 4
      $this->_debug->displayError('Can\'t execute an empty Query', false);
433
434 4
      return false;
435
    }
436
437
    if (
438
        $params !== false
439 27
        &&
440 2
        is_array($params)
441 27
        &&
442 2
        count($params) > 0
443 27
    ) {
444 2
      $sql = $this->_parseQueryParams($sql, $params);
445 2
    }
446
447 27
    $query_start_time = microtime(true);
448 27
    $query_result = mysqli_real_query($this->link, $sql);
449 27
    $query_duration = microtime(true) - $query_start_time;
450
451 27
    $this->query_count++;
452
453 27
    $mysqli_field_count = mysqli_field_count($this->link);
454 27
    if ($mysqli_field_count) {
455 24
      $result = mysqli_store_result($this->link);
456 24
    } else {
457 18
      $result = $query_result;
458
    }
459
460 27
    if ($result instanceof \mysqli_result) {
461
462
      // log the select query
463 23
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
464
465
      // return query result object
466 23
      return new Result($sql, $result);
467
468 19
    } elseif ($query_result === true) {
469
470
      // "INSERT" || "REPLACE"
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
471 17 View Code Duplication
      if (preg_match('/^\s*"?(INSERT|REPLACE)\s+/i', $sql)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
472 16
        $insert_id = (int)$this->insert_id();
473 16
        $this->_debug->logQuery($sql, $query_duration, $insert_id);
474
475 16
        return $insert_id;
476
      }
477
478
      // "UPDATE" || "DELETE"
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
479 7 View Code Duplication
      if (preg_match('/^\s*"?(UPDATE|DELETE)\s+/i', $sql)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
480 7
        $affected_rows = (int)$this->affected_rows();
481 7
        $this->_debug->logQuery($sql, $query_duration, $affected_rows);
482
483 7
        return $affected_rows;
484
      }
485
486
      // log the ? query
487
      $this->_debug->logQuery($sql, $query_duration, 0);
488
489
      return true;
490
    }
491
492
    // log the error query
493 8
    $this->_debug->logQuery($sql, $query_duration, 0, true);
494
495 8
    return $this->queryErrorHandling(mysqli_error($this->link), $sql, $params);
496
  }
497
498
  /**
499
   * _parseQueryParams
500
   *
501
   * @param string $sql
502
   * @param array  $params
503
   *
504
   * @return string
505
   */
506 2
  private function _parseQueryParams($sql, array $params)
507
  {
508
    // is there anything to parse?
509 2
    if (strpos($sql, '?') === false) {
510
      return $sql;
511
    }
512
513 2
    if (count($params) > 0) {
514 2
      $parseKey = md5(uniqid(mt_rand(), true));
515 2
      $sql = str_replace('?', $parseKey, $sql);
516
517 2
      $k = 0;
518 2
      while (strpos($sql, $parseKey) !== false) {
519 2
        $value = $this->secure($params[$k]);
520 2
        $sql = preg_replace("/$parseKey/", $value, $sql, 1);
521 2
        $k++;
522 2
      }
523 2
    }
524
525 2
    return $sql;
526
  }
527
528
  /**
529
   * Try to secure a variable, so can you use it in sql-queries.
530
   *
531
   * <p>
532
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
533
   * 1. parse into (int)
534
   * </p><br />
535
   *
536
   * <p>
537
   * <strong>string:</strong><br />
538
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
539
   * 2. trim whitespace<br />
540
   * 3. trim '<br />
541
   * 4. escape the string (and remove non utf-8 chars)<br />
542
   * 5. trim ' again (because we maybe removed some chars)<br />
543
   * 6. add ' around the new string<br />
544
   * </p><br />
545
   *
546
   * <p>
547
   * <strong>array:</strong><br />
548
   * 1. return null
549
   * </p><br />
550
   *
551
   * <p>
552
   * <strong>object:</strong><br />
553
   * 1. return false
554
   * </p>
555
   *
556
   * @param mixed $var
557
   *
558
   * @return string | null
559
   */
560 20
  public function secure($var)
561
  {
562 20
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
563
      return $var;
564
    }
565
566 20
    if (is_string($var)) {
567 15
      $var = trim(trim($var), "'");
568 15
    }
569
570 20
    $var = $this->escape($var, false, false, null);
571
572 20
    if (is_string($var)) {
573 16
      $var = "'" . trim($var, "'") . "'";
574 16
    }
575
576 20
    return $var;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $var; (array|boolean|double|integer|string) is incompatible with the return type documented by voku\db\DB::secure of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
577
  }
578
579
  /**
580
   * Escape
581
   *
582
   * @param mixed $var boolean: convert into "integer"<br />
583
   *                   int: convert into "integer"<br />
584
   *                   float: convert into "float" and replace "," with "."<br />
585
   *                   array: run escape() for every key => value<br />
586
   *                   string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
587
   * @param bool  $stripe_non_utf8
588
   * @param bool  $html_entity_decode
589
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
590
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
591
   *                                 <strong>null</strong> => Convert the array into null, every time.
592
   *
593
   * @return array|bool|float|int|string
594
   */
595 26
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
596
  {
597
    // save the current value as int (for later usage)
598 26
    if (!is_object($var)) {
599 26
      $varInt = (int)$var;
600 26
    }
601
602
    /** @noinspection TypeUnsafeComparisonInspection */
603
    if (
604 26
        is_int($var)
605
        ||
606 26
        is_bool($var)
607 26
        ||
608 26
        (isset($varInt, $var[0]) && $var[0] != '0' && "$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...
609 26
    ) {
610
611
      // "int" || int || bool
612
613 18
      return (int)$var;
614
615 26
    } elseif (is_float($var)) {
616
617
      // float
618
619 3
      return number_format((float)str_replace(',', '.', $var), 8, '.', '');
620
621 26
    } elseif (is_array($var)) {
622
623
      // array
624
625 1
      if ($convert_array === null) {
626 1
        return null;
627
      }
628
629
      $varCleaned = array();
630
      foreach ($var as $key => $value) {
631
632
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
633
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
634
635
        /** @noinspection OffsetOperationsInspection */
636
        $varCleaned[$key] = $value;
637
      }
638
639
      if ($convert_array === true) {
640
        $varCleaned = implode(',', $varCleaned);
641
642
        return $varCleaned;
643
      } else {
644
        return (array)$varCleaned;
645
      }
646
    }
647
648 26
    if (is_string($var)) {
649
650
      // "string"
651
652 26
      if ($stripe_non_utf8 === true) {
653 8
        $var = UTF8::cleanup($var);
654 8
      }
655
656 26
      if ($html_entity_decode === true) {
657
        // use no-html-entity for db
658
        $var = UTF8::html_entity_decode($var);
659
      }
660
661 26
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
662
663 26
      $var = mysqli_real_escape_string($this->getLink(), $var);
664
665 26
      return (string)$var;
666
667 3
    } elseif ($var instanceof \DateTime) {
668
669
      // "DateTime"-object
670
671
      try {
672 3
        return $this->escape($var->format('Y-m-d H:i:s'), false);
673
      } catch (\Exception $e) {
674
        return null;
675
      }
676
677
    } else {
678 2
      return false;
679
    }
680
  }
681
682
  /**
683
   * Get the mysqli-link (link identifier returned by mysqli-connect).
684
   *
685
   * @return \mysqli
686
   */
687 28
  public function getLink()
688
  {
689 28
    return $this->link;
690
  }
691
692
  /**
693
   * Returns the auto generated id used in the last query.
694
   *
695
   * @return int|string
696
   */
697 16
  public function insert_id()
698
  {
699 16
    return mysqli_insert_id($this->link);
700
  }
701
702
  /**
703
   * Gets the number of affected rows in a previous MySQL operation.
704
   *
705
   * @return int
706
   */
707 7
  public function affected_rows()
708
  {
709 7
    return mysqli_affected_rows($this->link);
710
  }
711
712
  /**
713
   * Error-handling for the sql-query.
714
   *
715
   * @param string     $errorMsg
716
   * @param string     $sql
717
   * @param array|bool $sqlParams false if there wasn't any parameter
718
   *
719
   * @throws \Exception
720
   *
721
   * @return bool
722
   */
723 9 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...
724
  {
725 9
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
726 1
      static $reconnectCounter;
727
728
      // exit if we have more then 3 "DB server has gone away"-errors
729 1
      if ($reconnectCounter > 3) {
730
        $this->_debug->mailToAdmin('SQL-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
731
        throw new \Exception($errorMsg);
732
      } else {
733 1
        $this->_debug->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
734
735
        // reconnect
736 1
        $reconnectCounter++;
737 1
        $this->reconnect(true);
738
739
        // re-run the current query
740 1
        return $this->query($sql, $sqlParams);
741
      }
742
    } else {
743 8
      $this->_debug->mailToAdmin('SQL-Warning', $errorMsg . ":\n<br />" . $sql);
744
745
      // this query returned an error, we must display it (only for dev) !!!
746 8
      $this->_debug->displayError($errorMsg . ' | ' . $sql);
747
    }
748
749 8
    return false;
750
  }
751
752
  /**
753
   * Reconnect to the MySQL-Server.
754
   *
755
   * @param bool $checkViaPing
756
   *
757
   * @return bool
758
   */
759 3
  public function reconnect($checkViaPing = false)
760
  {
761 3
    $ping = false;
762
763 3
    if ($checkViaPing === true) {
764 2
      $ping = $this->ping();
765 2
    }
766
767 3
    if ($ping !== true) {
768 3
      $this->connected = false;
769 3
      $this->connect();
770 3
    }
771
772 3
    return $this->isReady();
773
  }
774
775
  /**
776
   * Pings a server connection, or tries to reconnect
777
   * if the connection has gone down.
778
   *
779
   * @return boolean
780
   */
781 3
  public function ping()
782
  {
783
    if (
784 3
        $this->link
785 3
        &&
786 3
        $this->link instanceof \mysqli
787 3
    ) {
788
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
789 3
      return @mysqli_ping($this->link);
790
    } else {
791
      return false;
792
    }
793
  }
794
795
  /**
796
   * Execute select/insert/update/delete sql-queries.
797
   *
798
   * @param string $query    sql-query
799
   * @param bool   $useCache use cache?
800
   * @param int    $cacheTTL cache-ttl in seconds
801
   *
802
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
803
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
804
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
805
   *               "true" by e.g. "DROP"-queries<br />
806
   *               "false" on error
807
   */
808 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
809
  {
810 3
    $db = self::getInstance();
811
812 3
    if ($useCache === true) {
813 1
      $cache = new Cache(null, null, false, $useCache);
814 1
      $cacheKey = 'sql-' . md5($query);
815
816
      if (
817 1
          $cache->getCacheIsReady() === true
818 1
          &&
819 1
          $cache->existsItem($cacheKey)
820 1
      ) {
821 1
        return $cache->getItem($cacheKey);
822
      }
823
824 1
    } else {
825 3
      $cache = false;
826
    }
827
828 3
    $result = $db->query($query);
829
830 3
    if ($result instanceof Result) {
831
832 1
      $return = $result->fetchAllArray();
833
834
      if (
835 1
          isset($cacheKey)
836 1
          &&
837
          $useCache === true
838 1
          &&
839
          $cache instanceof Cache
840 1
          &&
841 1
          $cache->getCacheIsReady() === true
842 1
      ) {
843 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
844 1
      }
845
846 1
    } else {
847 2
      $return = $result;
848
    }
849
850 3
    return $return;
851
  }
852
853
  /**
854
   * Get the current charset.
855
   *
856
   * @return string
857
   */
858 1
  public function get_charset()
859
  {
860 1
    return $this->charset;
861
  }
862
863
  /**
864
   * Set the current charset.
865
   *
866
   * @param string $charset
867
   *
868
   * @return bool
869
   */
870 7
  public function set_charset($charset)
871
  {
872 7
    $this->charset = (string)$charset;
873
874 7
    $return = mysqli_set_charset($this->link, $charset);
875
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
876 7
    @mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
877
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
878 7
    @mysqli_query($this->link, "SET NAMES '" . ($charset === 'utf8' ? 'utf8mb4' : $charset) . "'");
879
880 7
    return $return;
881
  }
882
883
  /**
884
   * Get all table-names via "SHOW TABLES".
885
   *
886
   * @return array
887
   */
888 1
  public function getAllTables()
889
  {
890 1
    $query = 'SHOW TABLES';
891 1
    $result = $this->query($query);
892
893 1
    return $result->fetchAllArray();
894
  }
895
896
  /**
897
   * Execute a sql-multi-query.
898
   *
899
   * @param string $sql
900
   *
901
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
902
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
903
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
904
   *                        "boolean" by only by e.g. "DROP"-queries<br />
905
   *
906
   * @throws \Exception
907
   */
908 1
  public function multi_query($sql)
909
  {
910 1
    if (!$this->isReady()) {
911
      return false;
912
    }
913
914 1 View Code Duplication
    if (!$sql || $sql === '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
915 1
      $this->_debug->displayError('Can\'t execute an empty Query', false);
916
917 1
      return false;
918
    }
919
920 1
    $query_start_time = microtime(true);
921 1
    $resultTmp = mysqli_multi_query($this->link, $sql);
922 1
    $query_duration = microtime(true) - $query_start_time;
923
924 1
    $this->_debug->logQuery($sql, $query_duration, 0);
925
926 1
    $returnTheResult = false;
927 1
    $result = array();
928 1
    if ($resultTmp) {
929
      do {
930 1
        $resultTmpInner = mysqli_store_result($this->link);
931
932 1
        if ($resultTmpInner instanceof \mysqli_result) {
933 1
          $returnTheResult = true;
934 1
          $result[] = new Result($sql, $resultTmpInner);
935 1
        } else {
936 1
          $errorMsg = mysqli_error($this->link);
937
938
          // is the query successful
939 1
          if ($resultTmpInner === true || !$errorMsg) {
940 1
            $result[] = true;
941 1
          } else {
942
            $result[] = $this->queryErrorHandling($errorMsg, $sql);
943
          }
944
        }
945 1
      } while (mysqli_more_results($this->link) === true ? mysqli_next_result($this->link) : false);
946
947 1
    } else {
948
949
      $errorMsg = mysqli_error($this->link);
950
951
      if ($this->_debug->checkForDev() === true) {
952
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
953
        echo 'Error:' . $errorMsg;
954
      }
955
956
      $this->_debug->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
957
    }
958
959
    // return the result only if there was a "SELECT"-query
960 1
    if ($returnTheResult === true) {
961 1
      return $result;
962
    }
963
964 1
    if (!in_array(false, $result, true)) {
965 1
      return true;
966
    } else {
967
      return false;
968
    }
969
  }
970
971
  /**
972
   * alias: "beginTransaction()"
973
   */
974 1
  public function startTransaction()
975
  {
976 1
    $this->beginTransaction();
977 1
  }
978
979
  /**
980
   * Begins a transaction, by turning off auto commit.
981
   *
982
   * @return boolean this will return true or false indicating success of transaction
983
   */
984 4
  public function beginTransaction()
985
  {
986 4
    $this->clearErrors();
987
988 4
    if ($this->inTransaction() === true) {
989 1
      $this->_debug->displayError('Error mysql server already in transaction!', true);
990
991
      return false;
992 4
    } elseif (mysqli_connect_errno()) {
993
      $this->_debug->displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
994
995
      return false;
996
    } else {
997 4
      $this->_in_transaction = true;
998 4
      mysqli_autocommit($this->link, false);
999
1000 4
      return true;
1001
1002
    }
1003
  }
1004
1005
  /**
1006
   * Clear the errors in "_debug->_errors".
1007
   *
1008
   * @return bool
1009
   */
1010 4
  public function clearErrors()
1011
  {
1012 4
    return $this->_debug->clearErrors();
1013
  }
1014
1015
  /**
1016
   * Check if we are in a transaction.
1017
   *
1018
   * @return boolean
1019
   */
1020 4
  public function inTransaction()
1021
  {
1022 4
    return $this->_in_transaction;
1023
  }
1024
1025
  /**
1026
   * Ends a transaction and commits if no errors, then ends autocommit.
1027
   *
1028
   * @return boolean this will return true or false indicating success of transactions
1029
   */
1030 2
  public function endTransaction()
1031
  {
1032
1033 2
    if (!$this->errors()) {
1034 1
      mysqli_commit($this->link);
1035 1
      $return = true;
1036 1
    } else {
1037 1
      $this->rollback();
1038 1
      $return = false;
1039
    }
1040
1041 2
    mysqli_autocommit($this->link, true);
1042 2
    $this->_in_transaction = false;
1043
1044 2
    return $return;
1045
  }
1046
1047
  /**
1048
   * Get all errors from "$this->_errors".
1049
   *
1050
   * @return array|false false === on errors
1051
   */
1052 2
  public function errors()
1053
  {
1054 2
    $errors = $this->_debug->getErrors();
1055
1056 2
    return count($errors) > 0 ? $errors : false;
1057
  }
1058
1059
  /**
1060
   * Rollback in a transaction.
1061
   */
1062 2
  public function rollback()
1063
  {
1064
    // init
1065 2
    $return = false;
1066
1067 2
    if ($this->_in_transaction === true) {
1068 2
      $return = mysqli_rollback($this->link);
1069 2
      mysqli_autocommit($this->link, true);
1070 2
      $this->_in_transaction = false;
1071 2
    }
1072
1073 2
    return $return;
1074
  }
1075
1076
  /**
1077
   * Execute a "insert"-query.
1078
   *
1079
   * @param string $table
1080
   * @param array  $data
1081
   *
1082
   * @return false|int false on error
1083
   */
1084 15
  public function insert($table, $data = array())
1085
  {
1086 15
    $table = trim($table);
1087
1088 15
    if ($table === '') {
1089 2
      $this->_debug->displayError('invalid table name');
1090
1091 1
      return false;
1092
    }
1093
1094 14
    if (count($data) === 0) {
1095 3
      $this->_debug->displayError('empty data for INSERT');
1096
1097 2
      return false;
1098
    }
1099
1100 12
    $SET = $this->_parseArrayPair($data);
1101
1102 12
    $sql = 'INSERT INTO ' . $this->quote_string($table) . " SET $SET;";
1103
1104 12
    return $this->query($sql);
1105
  }
1106
1107
  /**
1108
   * Parses arrays with value pairs and generates SQL to use in queries.
1109
   *
1110
   * @param array  $arrayPair
1111
   * @param string $glue this is the separator
1112
   *
1113
   * @return string
1114
   */
1115 17
  private function _parseArrayPair($arrayPair, $glue = ',')
1116
  {
1117
    // init
1118 17
    $sql = '';
1119 17
    $pairs = array();
1120
1121
    /** @noinspection IsEmptyFunctionUsageInspection */
1122 17
    if (!empty($arrayPair)) {
1123
1124 17
      foreach ($arrayPair as $_key => $_value) {
1125 17
        $_connector = '=';
1126 17
        $_key_upper = strtoupper($_key);
1127
1128 17
        if (strpos($_key_upper, ' NOT') !== false) {
1129 2
          $_connector = 'NOT';
1130 2
        }
1131
1132 17
        if (strpos($_key_upper, ' IS') !== false) {
1133 1
          $_connector = 'IS';
1134 1
        }
1135
1136 17
        if (strpos($_key_upper, ' IS NOT') !== false) {
1137 1
          $_connector = 'IS NOT';
1138 1
        }
1139
1140 17
        if (strpos($_key_upper, ' IN') !== false) {
1141 1
          $_connector = 'IN';
1142 1
        }
1143
1144 17
        if (strpos($_key_upper, ' NOT IN') !== false) {
1145 1
          $_connector = 'NOT IN';
1146 1
        }
1147
1148 17
        if (strpos($_key_upper, ' BETWEEN') !== false) {
1149 1
          $_connector = 'BETWEEN';
1150 1
        }
1151
1152 17
        if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
1153 1
          $_connector = 'NOT BETWEEN';
1154 1
        }
1155
1156 17
        if (strpos($_key_upper, ' LIKE') !== false) {
1157 2
          $_connector = 'LIKE';
1158 2
        }
1159
1160 17
        if (strpos($_key_upper, ' NOT LIKE') !== false) {
1161 2
          $_connector = 'NOT LIKE';
1162 2
        }
1163
1164 17 View Code Duplication
        if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
1165 2
          $_connector = '>';
1166 2
        }
1167
1168 17 View Code Duplication
        if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
1169 1
          $_connector = '<';
1170 1
        }
1171
1172 17
        if (strpos($_key_upper, ' >=') !== false) {
1173 2
          $_connector = '>=';
1174 2
        }
1175
1176 17
        if (strpos($_key_upper, ' <=') !== false) {
1177 1
          $_connector = '<=';
1178 1
        }
1179
1180 17
        if (strpos($_key_upper, ' <>') !== false) {
1181 1
          $_connector = '<>';
1182 1
        }
1183
1184 17
        if (is_array($_value) === true) {
1185 1
          foreach ($_value as $oldKey => $oldValue) {
1186 1
            $_value[$oldKey] = $this->secure($oldValue);
1187 1
          }
1188
1189 1
          if ($_connector === 'NOT IN' || $_connector === 'IN') {
1190 1
            $_value = '(' . implode(',', $_value) . ')';
1191 1
          } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
1192 1
            $_value = '(' . implode(' AND ', $_value) . ')';
1193 1
          }
1194
1195 1
        } else {
1196 17
          $_value = $this->secure($_value);
1197
        }
1198
1199 17
        $quoteString = $this->quote_string(trim(str_ireplace($_connector, '', $_key)));
1200 17
        $pairs[] = ' ' . $quoteString . ' ' . $_connector . ' ' . $_value . " \n";
1201 17
      }
1202
1203 17
      $sql = implode($glue, $pairs);
1204 17
    }
1205
1206 17
    return $sql;
1207
  }
1208
1209
  /**
1210
   * Quote && Escape e.g. a table name string.
1211
   *
1212
   * @param string $str
1213
   *
1214
   * @return string
1215
   */
1216 20
  public function quote_string($str)
1217
  {
1218 20
    return '`' . $this->escape($str, false) . '`';
1219
  }
1220
1221
  /**
1222
   * Get errors from "$this->_errors".
1223
   *
1224
   * @return array
1225
   */
1226 1
  public function getErrors()
1227
  {
1228 1
    return $this->_debug->getErrors();
1229
  }
1230
1231
  /**
1232
   * Execute a "replace"-query.
1233
   *
1234
   * @param string $table
1235
   * @param array  $data
1236
   *
1237
   * @return false|int false on error
1238
   */
1239 1
  public function replace($table, $data = array())
1240
  {
1241
1242 1
    $table = trim($table);
1243
1244 1
    if ($table === '') {
1245 1
      $this->_debug->displayError('invalid table name');
1246
1247 1
      return false;
1248
    }
1249
1250 1
    if (count($data) === 0) {
1251 1
      $this->_debug->displayError('empty data for REPLACE');
1252
1253 1
      return false;
1254
    }
1255
1256
    // extracting column names
1257 1
    $columns = array_keys($data);
1258 1
    foreach ($columns as $k => $_key) {
1259
      /** @noinspection AlterInForeachInspection */
1260 1
      $columns[$k] = $this->quote_string($_key);
1261 1
    }
1262
1263 1
    $columns = implode(',', $columns);
1264
1265
    // extracting values
1266 1
    foreach ($data as $k => $_value) {
1267
      /** @noinspection AlterInForeachInspection */
1268 1
      $data[$k] = $this->secure($_value);
1269 1
    }
1270 1
    $values = implode(',', $data);
1271
1272 1
    $sql = 'REPLACE INTO ' . $this->quote_string($table) . " ($columns) VALUES ($values);";
1273
1274 1
    return $this->query($sql);
1275
  }
1276
1277
  /**
1278
   * Execute a "update"-query.
1279
   *
1280
   * @param string       $table
1281
   * @param array        $data
1282
   * @param array|string $where
1283
   *
1284
   * @return false|int false on error
1285
   */
1286 6
  public function update($table, $data = array(), $where = '1=1')
1287
  {
1288 6
    $table = trim($table);
1289
1290 6
    if ($table === '') {
1291 1
      $this->_debug->displayError('invalid table name');
1292
1293 1
      return false;
1294
    }
1295
1296 6
    if (count($data) === 0) {
1297 2
      $this->_debug->displayError('empty data for UPDATE');
1298
1299 2
      return false;
1300
    }
1301
1302 6
    $SET = $this->_parseArrayPair($data);
1303
1304 6
    if (is_string($where)) {
1305 2
      $WHERE = $this->escape($where, false);
1306 6
    } elseif (is_array($where)) {
1307 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
1308 4
    } else {
1309 1
      $WHERE = '';
1310
    }
1311
1312 6
    $sql = 'UPDATE ' . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1313
1314 6
    return $this->query($sql);
1315
  }
1316
1317
  /**
1318
   * Execute a "delete"-query.
1319
   *
1320
   * @param string       $table
1321
   * @param string|array $where
1322
   *
1323
   * @return false|int false on error
1324
   */
1325 1 View Code Duplication
  public function delete($table, $where)
1326
  {
1327
1328 1
    $table = trim($table);
1329
1330 1
    if ($table === '') {
1331 1
      $this->_debug->displayError('invalid table name');
1332
1333 1
      return false;
1334
    }
1335
1336 1
    if (is_string($where)) {
1337 1
      $WHERE = $this->escape($where, false);
1338 1
    } elseif (is_array($where)) {
1339 1
      $WHERE = $this->_parseArrayPair($where, 'AND');
1340 1
    } else {
1341 1
      $WHERE = '';
1342
    }
1343
1344 1
    $sql = 'DELETE FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1345
1346 1
    return $this->query($sql);
1347
  }
1348
1349
  /**
1350
   * Execute a "select"-query.
1351
   *
1352
   * @param string       $table
1353
   * @param string|array $where
1354
   *
1355
   * @return false|Result false on error
1356
   */
1357 18 View Code Duplication
  public function select($table, $where = '1=1')
1358
  {
1359
1360 18
    if ($table === '') {
1361 1
      $this->_debug->displayError('invalid table name');
1362
1363 1
      return false;
1364
    }
1365
1366 18
    if (is_string($where)) {
1367 3
      $WHERE = $this->escape($where, false);
1368 18
    } elseif (is_array($where)) {
1369 16
      $WHERE = $this->_parseArrayPair($where, 'AND');
1370 16
    } else {
1371 1
      $WHERE = '';
1372
    }
1373
1374 18
    $sql = 'SELECT * FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1375
1376 18
    return $this->query($sql);
1377
  }
1378
1379
  /**
1380
   * Get the last sql-error.
1381
   *
1382
   * @return string false on error
1383
   */
1384 1
  public function lastError()
1385
  {
1386 1
    $errors = $this->_debug->getErrors();
1387
1388 1
    return count($errors) > 0 ? end($errors) : false;
1389
  }
1390
1391
  /**
1392
   * @return Debug
1393
   */
1394 7
  public function getDebugger()
1395
  {
1396 7
    return $this->_debug;
1397
  }
1398
1399
  /**
1400
   * __destruct
1401
   *
1402
   */
1403
  public function __destruct()
1404
  {
1405
    // close the connection only if we don't save PHP-SESSION's in DB
1406
    if ($this->session_to_db === false) {
1407
      $this->close();
1408
    }
1409
  }
1410
1411
  /**
1412
   * Closes a previously opened database connection.
1413
   */
1414 2
  public function close()
1415
  {
1416 2
    $this->connected = false;
1417
1418 2
    if ($this->link) {
1419 2
      mysqli_close($this->link);
1420 2
    }
1421 2
  }
1422
1423
  /**
1424
   * Prevent the instance from being cloned.
1425
   *
1426
   * @return void
1427
   */
1428
  private function __clone()
1429
  {
1430
  }
1431
1432
  /**
1433
   * __wakeup
1434
   *
1435
   * @return void
1436
   */
1437 2
  public function __wakeup()
1438
  {
1439 2
    $this->reconnect();
1440 2
  }
1441
1442
}
1443