Completed
Push — master ( 46623e...0d11f8 )
by Lars
02:05
created

DB   F

Complexity

Total Complexity 368

Size/Duplication

Total Lines 2748
Duplicated Lines 7.79 %

Coupling/Cohesion

Components 2
Dependencies 13

Test Coverage

Coverage 75.73%

Importance

Changes 0
Metric Value
wmc 368
lcom 2
cbo 13
dl 214
loc 2748
ccs 724
cts 956
cp 0.7573
rs 0.8
c 0
b 0
f 0

62 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 60 1
A __clone() 0 3 1
A __destruct() 0 7 2
A __invoke() 0 4 2
A __wakeup() 0 4 1
B _loadConfig() 0 55 7
F _parseArrayPair() 14 155 44
B _parseQueryParams() 0 34 6
B _parseQueryParamsByName() 0 46 8
A affected_rows() 0 12 3
B beginTransaction() 32 32 7
A clearErrors() 0 4 1
B close() 0 39 6
B commit() 28 28 6
C connect() 0 95 13
A connect_helper() 0 19 4
B delete() 31 34 6
B endTransaction() 0 31 7
A errors() 0 6 2
F escape() 7 109 24
B execSQL() 14 48 10
A getAllTables() 0 9 1
A getConfig() 0 21 2
A getDebugger() 0 4 1
A getDoctrineConnection() 0 4 1
A getDoctrinePDOConnection() 11 11 3
A getErrors() 0 4 1
C getInstance() 0 80 12
A getInstanceDoctrineHelper() 0 25 1
A getLink() 0 4 1
A get_charset() 0 4 1
A inTransaction() 0 4 1
B insert() 0 37 6
A insert_id() 0 13 3
A isDoctrineMySQLiConnection() 0 11 3
A isDoctrinePDOConnection() 11 11 3
A isReady() 0 4 2
A lastError() 0 6 2
F multi_query() 0 126 24
A num_rows() 0 14 3
B ping() 0 22 7
A prepare() 0 4 1
A qry() 0 25 2
F query() 0 158 24
A queryWarningHandling() 0 20 5
B queryErrorHandling() 0 53 8
A quote_string() 0 13 1
A reconnect() 0 14 3
B replace() 0 42 7
B rollback() 29 29 6
C secure() 7 63 13
B select() 30 33 6
A select_db() 0 19 5
C setConfigExtra() 0 38 10
B set_charset() 0 41 10
A set_convert_null_to_empty_string() 0 6 1
A set_mysqli_report() 0 12 3
B showConfigError() 0 35 9
A startTransaction() 0 4 1
A table_exists() 0 10 3
A transact() 0 23 4
B update() 0 48 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DB often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DB, and based on these observations, apply Extract Interface, too.

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

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
644
    {
645 18
        if ($this->in_transaction) {
646 6
            $this->debug->displayError('Error: mysql server already in transaction!', false);
647
648 6
            return false;
649
        }
650
651 18
        $this->clearErrors(); // needed for "$this->endTransaction()"
652 18
        $this->in_transaction = true;
653
654 18
        if ($this->mysqli_link) {
655 18
            $return = \mysqli_autocommit($this->mysqli_link, false);
656
        } elseif ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
657
            $this->doctrine_connection->setAutoCommit(false);
658
            $this->doctrine_connection->beginTransaction();
659
660
            if ($this->doctrine_connection->isTransactionActive()) {
661
                $return = true;
662
            } else {
663
                $return = false;
664
            }
665
        } else {
666
            $return = false;
667
        }
668
669 18
        if (!$return) {
670
            $this->in_transaction = false;
671
        }
672
673 18
        return $return;
674
    }
675
676
    /**
677
     * Clear the errors in "_debug->_errors".
678
     *
679
     * @return bool
680
     */
681 18
    public function clearErrors(): bool
682
    {
683 18
        return $this->debug->clearErrors();
684
    }
685
686
    /**
687
     * Closes a previously opened database connection.
688
     *
689
     * @return bool
690
     *              Will return "true", if the connection was closed,
691
     *              otherwise (e.g. if the connection was already closed) "false".
692
     */
693 6
    public function close(): bool
694
    {
695 6
        $this->connected = false;
696
697
        if (
698 6
            $this->doctrine_connection
699
            &&
700 6
            $this->doctrine_connection instanceof \Doctrine\DBAL\Connection
701
        ) {
702
            $connectedBefore = $this->doctrine_connection->isConnected();
703
704
            $this->doctrine_connection->close();
705
706
            $this->mysqli_link = null;
707
708
            if ($connectedBefore) {
709
                \assert($this->doctrine_connection instanceof \Doctrine\DBAL\Connection);
710
711
                return !$this->doctrine_connection->isConnected();
712
            }
713
714
            return false;
715
        }
716
717
        if (
718 6
            $this->mysqli_link
719
            &&
720 6
            $this->mysqli_link instanceof \mysqli
721
        ) {
722 6
            $result = \mysqli_close($this->mysqli_link);
723 6
            $this->mysqli_link = null;
724
725 6
            return $result;
726
        }
727
728 3
        $this->mysqli_link = null;
729
730 3
        return false;
731
    }
732
733
    /**
734
     * Commits the current transaction and end the transaction.
735
     *
736
     * @return bool
737
     *              <p>bool true on success, false otherwise.</p>
738
     */
739 9 View Code Duplication
    public function commit(): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
740
    {
741 9
        if (!$this->in_transaction) {
742
            $this->debug->displayError('Error: mysql server is not in transaction!', false);
743
744
            return false;
745
        }
746
747 9
        if ($this->mysqli_link) {
748 9
            $return = \mysqli_commit($this->mysqli_link);
749 9
            \mysqli_autocommit($this->mysqli_link, true);
750
        } elseif ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
751
            $this->doctrine_connection->commit();
752
            $this->doctrine_connection->setAutoCommit(true);
753
754
            if ($this->doctrine_connection->isAutoCommit()) {
755
                $return = true;
756
            } else {
757
                $return = false;
758
            }
759
        } else {
760
            $return = false;
761
        }
762
763 9
        $this->in_transaction = false;
764
765 9
        return $return;
766
    }
767
768
    /**
769
     * Open a new connection to the MySQL server.
770
     *
771
     * @throws DBConnectException
772
     *
773
     * @return bool
774
     */
775 21
    public function connect(): bool
776
    {
777 21
        if ($this->isReady()) {
778 3
            return true;
779
        }
780
781 21
        if ($this->doctrine_connection) {
782
            $this->doctrine_connection->connect();
783
784
            $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
785
786
            if ($this->isDoctrineMySQLiConnection()) {
787
                \assert($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliConnection);
788
789
                $this->mysqli_link = $doctrineWrappedConnection->getWrappedResourceHandle();
790
791
                return $this->connect_helper();
792
            }
793
794
            if ($this->isDoctrinePDOConnection()) {
795
                $this->mysqli_link = null;
796
797
                return $this->connect_helper();
798
            }
799
        }
800
801 21
        $flags = $this->flags;
802
803 21
        \mysqli_report(\MYSQLI_REPORT_STRICT);
804
805
        try {
806 21
            $this->mysqli_link = \mysqli_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like \mysqli_init() of type object<mysql> is incompatible with the declared type object<mysqli>|null of property $mysqli_link.

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

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

Loading history...
807
808 21
            if (Helper::isMysqlndIsUsed()) {
809 21
                \mysqli_options($this->mysqli_link, \MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
810
            }
811
812 21
            if ($this->ssl) {
813
                if (empty($this->clientcert)) {
814
                    throw new DBConnectException('Error connecting to mysql server: clientcert not defined');
815
                }
816
817
                if (empty($this->clientkey)) {
818
                    throw new DBConnectException('Error connecting to mysql server: clientkey not defined');
819
                }
820
821
                if (empty($this->cacert)) {
822
                    throw new DBConnectException('Error connecting to mysql server: cacert not defined');
823
                }
824
825
                \mysqli_options($this->mysqli_link, \MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
826
827
                \mysqli_ssl_set(
828
                    $this->mysqli_link,
829
                    $this->clientkey,
830
                    $this->clientcert,
831
                    $this->cacert,
832
                    '',
833
                    ''
834
                );
835
836
                $flags |= \MYSQLI_CLIENT_SSL;
837
            }
838
839
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
840 21
            $this->connected = @\mysqli_real_connect(
841 21
                $this->mysqli_link,
842 21
                $this->hostname,
843 21
                $this->username,
844 21
                $this->password,
845 21
                $this->database,
846 21
                $this->port,
847 21
                $this->socket,
848 21
                (int) $flags
849
            );
850 9
        } catch (\Exception $e) {
851 9
            $error = 'Error connecting to mysql server: ' . $e->getMessage();
852 9
            $this->debug->displayError($error, false);
853
854 9
            throw new DBConnectException($error, 100, $e);
855
        }
856 12
        \mysqli_report(\MYSQLI_REPORT_OFF);
857
858 12
        $errno = \mysqli_connect_errno();
859 12
        if (!$this->connected || $errno) {
860
            $error = 'Error connecting to mysql server: ' . \mysqli_connect_error() . ' (' . $errno . ')';
861
            $this->debug->displayError($error, false);
862
863
            throw new DBConnectException($error, 101);
864
        }
865
866 12
        $this->set_charset($this->charset);
867
868 12
        return $this->isReady();
869
    }
870
871
    /**
872
     * @return bool
873
     */
874
    private function connect_helper(): bool
875
    {
876
        if (!$this->doctrine_connection) {
877
            $this->connected = false;
878
        } else {
879
            $this->connected = $this->doctrine_connection->isConnected();
880
        }
881
882
        if (!$this->connected) {
883
            $error = 'Error connecting to mysql server: ' . \print_r($this->doctrine_connection ? $this->doctrine_connection->errorInfo() : [], false);
884
            $this->debug->displayError($error, false);
885
886
            throw new DBConnectException($error, 101);
887
        }
888
889
        $this->set_charset($this->charset);
890
891
        return $this->isReady();
892
    }
893
894
    /**
895
     * Execute a "delete"-query.
896
     *
897
     * @param string       $table
898
     * @param array|string $where
899
     * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
900
     *
901
     * @throws QueryException
902
     *
903
     * @return false|int
904
     *                   <p>false on error</p>
905
     */
906 4 View Code Duplication
    public function delete(
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...
907
        string $table,
908
        $where,
909
        string $databaseName = null
910
    ) {
911
        // init
912 4
        $table = \trim($table);
913
914 4
        if ($table === '') {
915 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
916
917 3
            return false;
918
        }
919
920 4
        if (\is_string($where)) {
921 3
            $WHERE = $this->escape($where, false);
922 4
        } elseif (\is_array($where)) {
923 4
            $WHERE = $this->_parseArrayPair($where, 'AND');
924
        } else {
925 3
            $WHERE = '';
926
        }
927
928 4
        if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
929
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
930
        }
931
932 4
        $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE (${WHERE})";
933
934 4
        $return = $this->query($sql);
935
936 4
        \assert(\is_int($return) || $return === false);
937
938 4
        return $return;
939
    }
940
941
    /**
942
     * Ends a transaction and commits if no errors, then ends autocommit.
943
     *
944
     * @return bool
945
     *              <p>This will return true or false indicating success of transactions.</p>
946
     */
947 12
    public function endTransaction(): bool
948
    {
949 12
        if (!$this->in_transaction) {
950
            $this->debug->displayError('Error: mysql server is not in transaction!', false);
951
952
            return false;
953
        }
954
955 12
        if (!$this->errors()) {
956 3
            $return = $this->commit();
957
        } else {
958 9
            $this->rollback();
959 9
            $return = false;
960
        }
961
962 12
        if ($this->mysqli_link) {
963 12
            \mysqli_autocommit($this->mysqli_link, true);
964
        } elseif ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
965
            $this->doctrine_connection->setAutoCommit(true);
966
967
            if ($this->doctrine_connection->isAutoCommit()) {
968
                $return = true;
969
            } else {
970
                $return = false;
971
            }
972
        }
973
974 12
        $this->in_transaction = false;
975
976 12
        return $return;
977
    }
978
979
    /**
980
     * Get all errors from "$this->errors".
981
     *
982
     * @return array|false
983
     *                     <p>false === on errors</p>
984
     */
985 12
    public function errors()
986
    {
987 12
        $errors = $this->debug->getErrors();
988
989 12
        return \count($errors) > 0 ? $errors : false;
990
    }
991
992
    /**
993
     * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
994
     *
995
     * @param mixed     $var           bool: convert into "integer"<br />
996
     *                                 int: int (don't change it)<br />
997
     *                                 float: float (don't change it)<br />
998
     *                                 null: null (don't change it)<br />
999
     *                                 array: run escape() for every key => value<br />
1000
     *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
1001
     * @param bool      $stripe_non_utf8
1002
     * @param bool      $html_entity_decode
1003
     * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
1004
     *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
1005
     *                                 <strong>null</strong> => Convert the array into null, every time.
1006
     *
1007
     * @return mixed
1008
     */
1009 111
    public function escape(
1010
        $var = '',
1011
        bool $stripe_non_utf8 = true,
1012
        bool $html_entity_decode = false,
1013
        $convert_array = false
1014
    ) {
1015
        // [empty]
1016 111
        if ($var === '') {
1017 6
            return '';
1018
        }
1019
1020
        // ''
1021 111
        if ($var === "''") {
1022
            return "''";
1023
        }
1024
1025
        // check the type
1026 111
        $type = \gettype($var);
1027
1028 111
        if ($type === 'object') {
1029 9
            if ($var instanceof \DateTimeInterface) {
1030 9
                $var = $var->format('Y-m-d H:i:s');
1031 9
                $type = 'string';
1032 6
            } elseif (\method_exists($var, '__toString')) {
1033 6
                $var = (string) $var;
1034 6
                $type = 'string';
1035
            }
1036
        }
1037
1038
        switch ($type) {
1039 111
            case 'boolean':
1040 9
                $var = (int) $var;
1041
1042 9
                break;
1043
1044 111
            case 'double':
1045 111
            case 'integer':
1046 66
                break;
1047
1048 108
            case 'string':
1049 108
                if ($stripe_non_utf8) {
1050 23
                    $var = UTF8::cleanup($var);
1051
                }
1052
1053 108
                if ($html_entity_decode) {
1054 3
                    $var = UTF8::html_entity_decode($var);
1055
                }
1056
1057
                /** @noinspection PhpUsageOfSilenceOperatorInspection */
1058 108
                $var = @\get_magic_quotes_gpc() ? \stripslashes($var) : $var;
1059
1060
                if (
1061 108
                    $this->mysqli_link
1062
                    &&
1063 108
                    $this->mysqli_link instanceof \mysqli
1064
                ) {
1065 108
                    $var = \mysqli_real_escape_string($this->mysqli_link, $var);
1066
                } elseif ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
1067
                    $pdoConnection = $this->getDoctrinePDOConnection();
1068
                    \assert($pdoConnection !== false);
1069
                    $var = $pdoConnection->quote($var);
1070
                    $var = \substr($var, 1, -1);
1071
                }
1072
1073 108
                break;
1074
1075 9
            case 'array':
1076 6
                if ($convert_array === null) {
1077 3
                    if ($this->convert_null_to_empty_string) {
1078
                        $var = "''";
1079
                    } else {
1080 3
                        $var = 'NULL';
1081
                    }
1082
                } else {
1083 6
                    $varCleaned = [];
1084 6
                    foreach ((array) $var as $key => $value) {
1085 6
                        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
1086 6
                        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
1087
1088
                        /** @noinspection OffsetOperationsInspection */
1089 6
                        $varCleaned[$key] = $value;
1090
                    }
1091
1092 6 View Code Duplication
                    if ($convert_array === true) {
1093 3
                        $varCleaned = \implode(',', $varCleaned);
1094
1095 3
                        $var = $varCleaned;
1096
                    } else {
1097 6
                        $var = $varCleaned;
1098
                    }
1099
                }
1100
1101 6
                break;
1102
1103 9
            case 'NULL':
1104 6
                if ($this->convert_null_to_empty_string) {
1105
                    $var = "''";
1106
                } else {
1107 6
                    $var = 'NULL';
1108
                }
1109
1110 6
                break;
1111
1112
            default:
1113 6
                throw new \InvalidArgumentException(\sprintf('Not supported value "%s" of type %s.', \print_r($var, true), $type));
1114
        }
1115
1116 111
        return $var;
1117
    }
1118
1119
    /**
1120
     * Execute select/insert/update/delete sql-queries.
1121
     *
1122
     * @param string  $query    <p>sql-query</p>
1123
     * @param bool    $useCache optional <p>use cache?</p>
1124
     * @param int     $cacheTTL optional <p>cache-ttl in seconds</p>
1125
     * @param DB|null $db       optional <p>the database connection</p>
1126
     *
1127
     * @throws QueryException
1128
     *
1129
     * @return mixed
1130
     *               <ul>
1131
     *               <li>"array" by "<b>SELECT</b>"-queries</li>
1132
     *               <li>"int|string" (insert_id) by "<b>INSERT</b>"-queries</li>
1133
     *               <li>"int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries</li>
1134
     *               <li>"true" by e.g. "DROP"-queries</li>
1135
     *               <li>"false" on error</li>
1136
     *               </ul>
1137
     */
1138 9
    public static function execSQL(string $query, bool $useCache = false, int $cacheTTL = 3600, self $db = null)
1139
    {
1140
        // init
1141 9
        $cacheKey = null;
1142 9
        if (!$db) {
1143 9
            $db = self::getInstance();
1144
        }
1145
1146 9 View Code Duplication
        if ($useCache) {
1147 3
            $cache = new \voku\cache\Cache(null, null, false, $useCache);
1148 3
            $cacheKey = 'sql-' . \md5($query);
1149
1150
            if (
1151 3
                $cache->getCacheIsReady()
1152
                &&
1153 3
                $cache->existsItem($cacheKey)
1154
            ) {
1155 3
                return $cache->getItem($cacheKey);
1156
            }
1157
        } else {
1158 9
            $cache = false;
1159
        }
1160
1161 9
        $result = $db->query($query);
1162
1163 9
        if ($result instanceof Result) {
1164
            // save into the cache
1165
            if (
1166 3
                $cacheKey !== null
1167
                &&
1168 3
                $useCache
1169
                &&
1170 3
                $cache instanceof \voku\cache\Cache
1171
                &&
1172 3
                $cache->getCacheIsReady()
1173
            ) {
1174 1
                $return = $result->fetchAllArrayy();
1175
1176 1
                $cache->setItem($cacheKey, $return, $cacheTTL);
1177
            } else {
1178 3
                $return = $result->fetchAllArrayyYield();
1179
            }
1180
        } else {
1181 6
            $return = $result;
1182
        }
1183
1184 9
        return $return;
1185
    }
1186
1187
    /**
1188
     * Get all table-names via "SHOW TABLES".
1189
     *
1190
     * @return \Arrayy\Arrayy
1191
     */
1192 3
    public function getAllTables(): \Arrayy\Arrayy
1193
    {
1194 3
        $query = 'SHOW TABLES';
1195 3
        $result = $this->query($query);
1196
1197 3
        \assert($result instanceof Result);
1198
1199 3
        return $result->fetchAllArrayyYield();
1200
    }
1201
1202
    /**
1203
     * @return array
1204
     */
1205 49
    public function getConfig(): array
1206
    {
1207
        $config = [
1208 49
            'hostname'   => $this->hostname,
1209 49
            'username'   => $this->username,
1210 49
            'password'   => $this->password,
1211 49
            'port'       => $this->port,
1212 49
            'database'   => $this->database,
1213 49
            'socket'     => $this->socket,
1214 49
            'charset'    => $this->charset,
1215 49
            'cacert'     => $this->cacert,
1216 49
            'clientcert' => $this->clientcert,
1217 49
            'clientkey'  => $this->clientkey,
1218
        ];
1219
1220 49
        if ($this->doctrine_connection instanceof \Doctrine\DBAL\Connection) {
1221
            $config += $this->doctrine_connection->getParams();
1222
        }
1223
1224 49
        return $config;
1225
    }
1226
1227
    /**
1228
     * @return Debug
1229
     */
1230 10
    public function getDebugger(): Debug
1231
    {
1232 10
        return $this->debug;
1233
    }
1234
1235
    /**
1236
     * @return \Doctrine\DBAL\Connection|null
1237
     */
1238 2
    public function getDoctrineConnection()
1239
    {
1240 2
        return $this->doctrine_connection;
1241
    }
1242
1243
    /**
1244
     * @return \Doctrine\DBAL\Driver\Connection|false
1245
     */
1246 View Code Duplication
    private function getDoctrinePDOConnection()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1247
    {
1248
        if ($this->doctrine_connection) {
1249
            $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1250
            if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
1251
                return $doctrineWrappedConnection;
1252
            }
1253
        }
1254
1255
        return false;
1256
    }
1257
1258
    /**
1259
     * Get errors from "$this->errors".
1260
     *
1261
     * @return array
1262
     */
1263 3
    public function getErrors(): array
1264
    {
1265 3
        return $this->debug->getErrors();
1266
    }
1267
1268
    /**
1269
     * @param string $hostname              <p>Hostname of the mysql server</p>
1270
     * @param string $username              <p>Username for the mysql connection</p>
1271
     * @param string $password              <p>Password for the mysql connection</p>
1272
     * @param string $database              <p>Database for the mysql connection</p>
1273
     * @param int    $port                  <p>default is (int)3306</p>
1274
     * @param string $charset               <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1275
     * @param bool   $exit_on_error         <p>Throw a 'Exception' when a query failed, otherwise it will return
1276
     *                                      'false'. Use false to disable it.</p>
1277
     * @param bool   $echo_on_error         <p>Echo the error if "checkForDev()" returns true.
1278
     *                                      Use false to disable it.</p>
1279
     * @param string $logger_class_name
1280
     * @param string $logger_level          <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1281
     * @param array  $extra_config          <p>
1282
     *                                      're_connect'    => bool<br>
1283
     *                                      'session_to_db' => bool<br>
1284
     *                                      'doctrine'      => \Doctrine\DBAL\Connection<br>
1285
     *                                      'socket'        => string (path)<br>
1286
     *                                      'flags'         => null|int<br>
1287
     *                                      'ssl'           => bool<br>
1288
     *                                      'clientkey'     => string (path)<br>
1289
     *                                      'clientcert'    => string (path)<br>
1290
     *                                      'cacert'        => string (path)<br>
1291
     *                                      </p>
1292
     *
1293
     * @return self
1294
     */
1295 208
    public static function getInstance(
1296
        string $hostname = '',
1297
        string $username = '',
1298
        string $password = '',
1299
        string $database = '',
1300
        $port = 3306,
1301
        string $charset = 'utf8',
1302
        bool $exit_on_error = true,
1303
        bool $echo_on_error = true,
1304
        string $logger_class_name = '',
1305
        string $logger_level = '',
1306
        array $extra_config = []
1307
    ): self {
1308
        /**
1309
         * @var self[]
1310
         */
1311 208
        static $instance = [];
1312
1313
        /**
1314
         * @var self|null
1315
         */
1316 208
        static $firstInstance = null;
1317
1318
        // fallback
1319 208
        if (!$charset) {
1320 121
            $charset = 'utf8';
1321
        }
1322
1323
        if (
1324 208
            '' . $hostname . $username . $password . $database . $port . $charset === '' . $port . $charset
1325
            &&
1326 208
            $firstInstance instanceof self
1327
        ) {
1328 123
            if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1329
                $firstInstance->reconnect(true);
1330
            }
1331
1332 123
            return $firstInstance;
1333
        }
1334
1335 127
        $extra_config_string = '';
1336 127
        foreach ($extra_config as $extra_config_key => $extra_config_value) {
1337 56
            if (\is_object($extra_config_value)) {
1338
                $extra_config_value_tmp = \spl_object_hash($extra_config_value);
1339
            } else {
1340 56
                $extra_config_value_tmp = (string) $extra_config_value;
1341
            }
1342 56
            $extra_config_string .= $extra_config_key . $extra_config_value_tmp;
1343
        }
1344
1345 127
        $connection = \md5(
1346 127
            $hostname . $username . $password . $database . $port . $charset . (int) $exit_on_error . (int) $echo_on_error . $logger_class_name . $logger_level . $extra_config_string
1347
        );
1348
1349 127
        if (!isset($instance[$connection])) {
1350 24
            $instance[$connection] = new self(
1351 24
                $hostname,
1352 24
                $username,
1353 24
                $password,
1354 24
                $database,
1355 24
                $port,
1356 24
                $charset,
1357 24
                $exit_on_error,
1358 24
                $echo_on_error,
1359 24
                $logger_class_name,
1360 24
                $logger_level,
1361 24
                $extra_config
1362
            );
1363
1364 6
            if ($firstInstance === null) {
1365 1
                $firstInstance = $instance[$connection];
1366
            }
1367
        }
1368
1369 115
        if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1370
            $instance[$connection]->reconnect(true);
1371
        }
1372
1373 115
        return $instance[$connection];
1374
    }
1375
1376
    /**
1377
     * @param \Doctrine\DBAL\Connection $doctrine
1378
     * @param string                    $charset       <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1379
     * @param bool                      $exit_on_error <p>Throw a 'Exception' when a query failed, otherwise it will
1380
     *                                                 return 'false'. Use false to disable it.</p>
1381
     * @param bool                      $echo_on_error <p>Echo the error if "checkForDev()" returns true.
1382
     *                                                 Use false to disable it.</p>
1383
     * @param string                    $logger_class_name
1384
     * @param string                    $logger_level  <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1385
     * @param array                     $extra_config  <p>
1386
     *                                                 're_connect'    => bool<br>
1387
     *                                                 'session_to_db' => bool<br>
1388
     *                                                 'socket'        => string (path)<br>
1389
     *                                                 'flags'         => null|int<br>
1390
     *                                                 'ssl'           => bool<br>
1391
     *                                                 'clientkey'     => string (path)<br>
1392
     *                                                 'clientcert'    => string (path)<br>
1393
     *                                                 'cacert'        => string (path)<br>
1394
     *                                                 </p>
1395
     *
1396
     * @return self
1397
     */
1398 55
    public static function getInstanceDoctrineHelper(
1399
        \Doctrine\DBAL\Connection $doctrine,
1400
        string $charset = 'utf8',
1401
        bool $exit_on_error = true,
1402
        bool $echo_on_error = true,
1403
        string $logger_class_name = '',
1404
        string $logger_level = '',
1405
        array $extra_config = []
1406
    ): self {
1407 55
        $extra_config['doctrine'] = $doctrine;
1408
1409 55
        return self::getInstance(
1410 55
            '',
1411 55
            '',
1412 55
            '',
1413 55
            '',
1414 55
            3306,
1415 55
            $charset,
1416 55
            $exit_on_error,
1417 55
            $echo_on_error,
1418 55
            $logger_class_name,
1419 55
            $logger_level,
1420 55
            $extra_config
1421
        );
1422
    }
1423
1424
    /**
1425
     * Get the mysqli-link (link identifier returned by mysqli-connect).
1426
     *
1427
     * @return \mysqli|null
1428
     */
1429 15
    public function getLink()
1430
    {
1431 15
        return $this->mysqli_link;
1432
    }
1433
1434
    /**
1435
     * Get the current charset.
1436
     *
1437
     * @return string
1438
     */
1439 3
    public function get_charset(): string
1440
    {
1441 3
        return $this->charset;
1442
    }
1443
1444
    /**
1445
     * Check if we are in a transaction.
1446
     *
1447
     * @return bool
1448
     */
1449
    public function inTransaction(): bool
1450
    {
1451
        return $this->in_transaction;
1452
    }
1453
1454
    /**
1455
     * Execute a "insert"-query.
1456
     *
1457
     * @param string      $table
1458
     * @param array       $data
1459
     * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1460
     *
1461
     * @throws QueryException
1462
     *
1463
     * @return false|int|string
1464
     *                   <p>false on error</p>
1465
     */
1466 74
    public function insert(
1467
        string $table,
1468
        array $data = [],
1469
        string $databaseName = null
1470
    ) {
1471
        // init
1472 74
        $table = \trim($table);
1473
1474 74
        if ($table === '') {
1475 6
            $this->debug->displayError('Invalid table name, table name in empty.', false);
1476
1477 6
            return false;
1478
        }
1479
1480 71
        if (\count($data) === 0) {
1481 9
            $this->debug->displayError('Invalid data for INSERT, data is empty.', false);
1482
1483 9
            return false;
1484
        }
1485
1486 65
        $SET = $this->_parseArrayPair($data);
1487
1488 65
        if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1489
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
1490
        }
1491
1492 65
        $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET ${SET}";
1493
1494 65
        $return = $this->query($sql);
1495 65
        if ($return === false) {
1496 3
            return false;
1497
        }
1498
1499 65
        \assert(\is_int($return) || \is_string($return));
1500
1501 65
        return $return;
1502
    }
1503
1504
    /**
1505
     * Returns the auto generated id used in the last query.
1506
     *
1507
     * @return false|int|string
1508
     */
1509 101
    public function insert_id()
1510
    {
1511 101
        if ($this->mysqli_link) {
1512 101
            return \mysqli_insert_id($this->mysqli_link);
1513
        }
1514
1515
        $doctrinePDOConnection = $this->getDoctrinePDOConnection();
1516
        if ($doctrinePDOConnection) {
1517
            return $doctrinePDOConnection->lastInsertId();
1518
        }
1519
1520
        return false;
1521
    }
1522
1523
    /**
1524
     * @return bool
1525
     */
1526
    public function isDoctrineMySQLiConnection(): bool
1527
    {
1528
        if ($this->doctrine_connection) {
1529
            $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1530
            if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliConnection) {
1531
                return true;
1532
            }
1533
        }
1534
1535
        return false;
1536
    }
1537
1538
    /**
1539
     * @return bool
1540
     */
1541 View Code Duplication
    public function isDoctrinePDOConnection(): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1542
    {
1543
        if ($this->doctrine_connection) {
1544
            $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1545
            if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
1546
                return true;
1547
            }
1548
        }
1549
1550
        return false;
1551
    }
1552
1553
    /**
1554
     * Check if db-connection is ready.
1555
     *
1556
     * @return bool
1557
     */
1558 163
    public function isReady(): bool
1559
    {
1560 163
        return $this->connected ? true : false;
1561
    }
1562
1563
    /**
1564
     * Get the last sql-error.
1565
     *
1566
     * @return false|string
1567
     *                      <p>false === there was no error</p>
1568
     */
1569 3
    public function lastError()
1570
    {
1571 3
        $errors = $this->debug->getErrors();
1572
1573 3
        return \count($errors) > 0 ? \end($errors) : false;
1574
    }
1575
1576
    /**
1577
     * Execute a sql-multi-query.
1578
     *
1579
     * @param string $sql
1580
     *
1581
     * @throws QueryException
1582
     *
1583
     * @return bool|Result[]
1584
     *                        <ul>
1585
     *                        <li>"Result"-Array by "<b>SELECT</b>"-queries</li>
1586
     *                        <li>"bool" by only "<b>INSERT</b>"-queries</li>
1587
     *                        <li>"bool" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries</li>
1588
     *                        <li>"bool" by only by e.g. "DROP"-queries</li>
1589
     *                        </ul>
1590
     */
1591 3
    public function multi_query(string $sql)
1592
    {
1593 3
        if (!$this->isReady()) {
1594
            return false;
1595
        }
1596
1597 3
        if (!$sql || $sql === '') {
1598 3
            $this->debug->displayError('Can not execute an empty query.', false);
1599
1600 3
            return false;
1601
        }
1602
1603 3
        if ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
1604
            $query_start_time = \microtime(true);
1605
            $queryException = null;
1606
            $query_result_doctrine = false;
1607
1608
            try {
1609
                $query_result_doctrine = $this->doctrine_connection->prepare($sql);
1610
                $resultTmp = $query_result_doctrine->execute();
1611
                $mysqli_field_count = $query_result_doctrine->columnCount();
1612
            } catch (\Exception $e) {
1613
                $resultTmp = false;
1614
                $mysqli_field_count = null;
1615
1616
                $queryException = $e;
1617
            }
1618
1619
            $query_duration = \microtime(true) - $query_start_time;
1620
1621
            $this->debug->logQuery($sql, $query_duration, 0);
1622
1623
            $returnTheResult = false;
1624
            $result = [];
1625
1626
            if ($resultTmp) {
1627
                if ($mysqli_field_count) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mysqli_field_count of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1628
                    if (
1629
                        $query_result_doctrine
1630
                        &&
1631
                        $query_result_doctrine instanceof \Doctrine\DBAL\Driver\Statement
1632
                    ) {
1633
                        $result = $query_result_doctrine;
1634
                    }
1635
                } else {
1636
                    $result = $resultTmp;
1637
                }
1638
1639
                if (
1640
                    $result instanceof \Doctrine\DBAL\Driver\Statement
1641
                    &&
1642
                    $result->columnCount() > 0
1643
                ) {
1644
                    $returnTheResult = true;
1645
1646
                    // return query result object
1647
                    $result = [new Result($sql, $result)];
1648
                } else {
1649
                    $result = [$result];
1650
                }
1651
            } else {
1652
1653
                // log the error query
1654
                $this->debug->logQuery($sql, $query_duration, 0, true);
1655
1656
                if (
1657
                    isset($queryException)
1658
                    &&
1659
                    $queryException instanceof \Doctrine\DBAL\Query\QueryException
1660
                ) {
1661
                    return $this->queryErrorHandling($queryException->getMessage(), $queryException->getCode(), $sql, false, true);
1662
                }
1663
            }
1664 3
        } elseif ($this->mysqli_link) {
1665 3
            $query_start_time = \microtime(true);
1666 3
            $resultTmp = \mysqli_multi_query($this->mysqli_link, $sql);
1667 3
            $query_duration = \microtime(true) - $query_start_time;
1668
1669 3
            $this->debug->logQuery($sql, $query_duration, 0);
1670
1671 3
            $returnTheResult = false;
1672 3
            $result = [];
1673
1674 3
            if ($resultTmp) {
1675
                do {
1676 3
                    $resultTmpInner = \mysqli_store_result($this->mysqli_link);
1677
1678 3
                    if ($resultTmpInner instanceof \mysqli_result) {
1679 3
                        $returnTheResult = true;
1680 3
                        $result[] = new Result($sql, $resultTmpInner);
1681 3
                    } elseif (\mysqli_errno($this->mysqli_link)) {
1682
                        $result[] = false;
1683
                    } else {
1684 3
                        $result[] = true;
1685
                    }
1686 3
                } while (\mysqli_more_results($this->mysqli_link) ? \mysqli_next_result($this->mysqli_link) : false);
1687
            } else {
1688
1689
                // log the error query
1690 3
                $this->debug->logQuery($sql, $query_duration, 0, true);
1691
1692 3
                return $this->queryErrorHandling(\mysqli_error($this->mysqli_link), \mysqli_errno($this->mysqli_link), $sql, false, true);
1693
            }
1694
        } else {
1695
1696
            // log the error query
1697
            $this->debug->logQuery($sql, 0, 0, true);
1698
1699
            return $this->queryErrorHandling('no database connection', 1, $sql, false, true);
1700
        }
1701
1702
        // return the result only if there was a "SELECT"-query
1703 3
        if ($returnTheResult) {
1704 3
            return $result;
1705
        }
1706
1707
        if (
1708 3
            \count($result) > 0
1709
            &&
1710 3
            !\in_array(false, $result, true)
1711
        ) {
1712 3
            return true;
1713
        }
1714
1715
        return false;
1716
    }
1717
1718
    /**
1719
     * Count number of rows found matching a specific query.
1720
     *
1721
     * @param string $query
1722
     *
1723
     * @return int
1724
     */
1725 3
    public function num_rows(string $query): int
1726
    {
1727 3
        $check = $this->query($query);
1728
1729
        if (
1730 3
            $check === false
1731
            ||
1732 3
            !$check instanceof Result
1733
        ) {
1734
            return 0;
1735
        }
1736
1737 3
        return $check->num_rows;
1738
    }
1739
1740
    /**
1741
     * Pings a server connection, or tries to reconnect
1742
     * if the connection has gone down.
1743
     *
1744
     * @return bool
1745
     *
1746
     * @noinspection PhpInconsistentReturnPointsInspection - false-positive
1747
     */
1748 9
    public function ping(): bool
1749
    {
1750 9
        if (!$this->connected) {
1751 3
            return false;
1752
        }
1753
1754 6
        if ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
1755
            return $this->doctrine_connection->ping();
1756
        }
1757
1758 6
        if (!$this->mysqli_link) {
1759
            return false;
1760
        }
1761
1762 6
        if ($this->mysqli_link->connect_errno) {
1763
            return false;
1764
        }
1765
1766 6
        if ($this->mysqli_link instanceof \mysqli) {
1767 6
            return \mysqli_ping($this->mysqli_link);
1768
        }
1769
    }
1770
1771
    /**
1772
     * Get a new "Prepare"-Object for your sql-query.
1773
     *
1774
     * @param string $query
1775
     *
1776
     * @return Prepare
1777
     */
1778 2
    public function prepare(string $query): Prepare
1779
    {
1780 2
        return new Prepare($this, $query);
1781
    }
1782
1783
    /**
1784
     * Execute a sql-query and return the result-array for select-statements.
1785
     *
1786
     * @param string $query
1787
     *
1788
     * @throws \Exception
1789
     *
1790
     * @return mixed
1791
     *
1792
     * @deprecated
1793
     */
1794 3
    public static function qry(string $query)
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1795
    {
1796 3
        $db = self::getInstance();
1797
1798 3
        $args = \func_get_args();
1799
        /** @noinspection SuspiciousAssignmentsInspection */
1800 3
        $query = \array_shift($args);
1801 3
        $query = \str_replace('?', '%s', $query);
1802 3
        $args = \array_map(
1803
            [
1804 3
                $db,
1805 3
                'escape',
1806
            ],
1807 3
            $args
1808
        );
1809 3
        \array_unshift($args, $query);
1810 3
        $query = \sprintf(...$args);
1811 3
        $result = $db->query($query);
1812
1813 3
        if ($result instanceof Result) {
1814 3
            return $result->fetchAllArrayyYield();
1815
        }
1816
1817 3
        return $result;
1818
    }
1819
1820
    /**
1821
     * Execute a sql-query.
1822
     *
1823
     * example:
1824
     * <code>
1825
     * $sql = "INSERT INTO TABLE_NAME_HERE
1826
     *   SET
1827
     *     foo = :foo,
1828
     *     bar = :bar
1829
     * ";
1830
     * $insert_id = $db->query(
1831
     *   $sql,
1832
     *   [
1833
     *     'foo' => 1.1,
1834
     *     'bar' => 1,
1835
     *   ]
1836
     * );
1837
     * </code>
1838
     *
1839
     * @param string     $sql               <p>The sql query-string.</p>
1840
     * @param array|bool $params            <p>
1841
     *                                      "array" of sql-query-parameters<br/>
1842
     *                                      "false" if you don't need any parameter (default)<br/>
1843
     *                                      </p>
1844
     *
1845
     * @throws QueryException
1846
     *
1847
     * @return bool|int|Result|string
1848
     *                                      <p>
1849
     *                                      "Result" by "<b>SELECT</b>"-queries<br />
1850
     *                                      "int|string" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
1851
     *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1852
     *                                      "true" by e.g. "DROP"-queries<br />
1853
     *                                      "false" on error
1854
     *                                      </p>
1855
     */
1856 141
    public function query(string $sql = '', $params = false)
1857
    {
1858 141
        if (!$this->isReady()) {
1859
            return false;
1860
        }
1861
1862 141
        if ($sql === '') {
1863 12
            $this->debug->displayError('Can not execute an empty query.', false);
1864
1865 12
            return false;
1866
        }
1867
1868
        if (
1869 135
            $params !== false
1870
            &&
1871 135
            \is_array($params)
1872
            &&
1873 135
            \count($params) > 0
1874
        ) {
1875 7
            $parseQueryParams = $this->_parseQueryParams($sql, $params);
1876 7
            $parseQueryParamsByName = $this->_parseQueryParamsByName($parseQueryParams['sql'], $parseQueryParams['params']);
1877 7
            $sql = $parseQueryParamsByName['sql'];
1878
        }
1879
1880
        // DEBUG
1881
        // var_dump($params);
1882
        // echo $sql . "\n";
1883
1884 135
        $query_start_time = \microtime(true);
1885 135
        $queryException = null;
1886 135
        $query_result_doctrine = false;
1887
1888 135
        if ($this->doctrine_connection) {
1889
            try {
1890
                $query_result_doctrine = $this->doctrine_connection->prepare($sql);
1891
                $query_result = $query_result_doctrine->execute();
1892
                $mysqli_field_count = $query_result_doctrine->columnCount();
1893
            } catch (\Exception $e) {
1894
                $query_result = false;
1895
                $mysqli_field_count = null;
1896
1897
                $queryException = $e;
1898
            }
1899 135
        } elseif ($this->mysqli_link) {
1900 135
            $query_result = \mysqli_real_query($this->mysqli_link, $sql);
1901 135
            $mysqli_field_count = \mysqli_field_count($this->mysqli_link);
1902
        } else {
1903
            $query_result = false;
1904
            $mysqli_field_count = null;
1905
1906
            $queryException = new DBConnectException('no mysql connection');
1907
        }
1908
1909 135
        $query_duration = \microtime(true) - $query_start_time;
1910
1911 135
        $this->query_count++;
1912
1913 135
        if ($mysqli_field_count) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mysqli_field_count of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1914 100
            if ($this->doctrine_connection) {
1915
                $result = false;
1916
                if (
1917
                    $query_result_doctrine
1918
                    &&
1919
                    $query_result_doctrine instanceof \Doctrine\DBAL\Driver\Statement
1920
                ) {
1921
                    $result = $query_result_doctrine;
1922
                }
1923 100
            } elseif ($this->mysqli_link) {
1924 100
                $result = \mysqli_store_result($this->mysqli_link);
1925
            } else {
1926 100
                $result = false;
1927
            }
1928
        } else {
1929 107
            $result = $query_result;
1930
        }
1931
1932
        if (
1933 135
            $result instanceof \Doctrine\DBAL\Driver\Statement
1934
            &&
1935 135
            $result->columnCount() > 0
1936
        ) {
1937
1938
            // log the select query
1939
            $this->debug->logQuery($sql, $query_duration, $mysqli_field_count);
1940
1941
            // check for mysql warnings
1942
            $this->queryWarningHandling($sql);
1943
1944
            // return query result object
1945
            return new Result($sql, $result);
1946
        }
1947
1948 135
        if ($result instanceof \mysqli_result) {
1949
1950
            // log the select query
1951 97
            $this->debug->logQuery($sql, $query_duration, $mysqli_field_count);
1952
1953
            // check for mysql warnings
1954 97
            $this->queryWarningHandling($sql);
1955
1956
            // return query result object
1957 97
            return new Result($sql, $result);
1958
        }
1959
1960 113
        if ($query_result) {
1961
1962
            // "INSERT" || "REPLACE"
1963 104
            if (\preg_match('/^\s*?(?:INSERT|REPLACE)\s+/i', $sql)) {
1964 101
                $insert_id = $this->insert_id();
1965
1966
                // log the query
1967 101
                $this->debug->logQuery($sql, $query_duration, $insert_id);
1968
1969
                // check for mysql warnings
1970 101
                $this->queryWarningHandling($sql);
1971
1972 101
                return $insert_id;
1973
            }
1974
1975
            // "UPDATE" || "DELETE"
1976 51
            if (\preg_match('/^\s*?(?:UPDATE|DELETE)\s+/i', $sql)) {
1977 25
                if ($this->mysqli_link) {
1978 25
                    $this->affected_rows = $this->affected_rows();
1979
                } elseif ($query_result_doctrine) {
1980
                    $this->affected_rows = $query_result_doctrine->rowCount();
1981
                }
1982
1983
                // log the query
1984 25
                $this->debug->logQuery($sql, $query_duration, $this->affected_rows);
1985
1986
                // check for mysql warnings
1987 25
                $this->queryWarningHandling($sql);
1988
1989 25
                return $this->affected_rows;
1990
            }
1991
1992
            // log the query
1993 26
            $this->debug->logQuery($sql, $query_duration, 0);
1994
1995
            // check for mysql warnings
1996 26
            $this->queryWarningHandling($sql);
1997
1998 26
            return true;
1999
        }
2000
2001
        // log the error query
2002 33
        $this->debug->logQuery($sql, $query_duration, 0, true);
2003
2004 33
        if ($queryException) {
2005
            return $this->queryErrorHandling($queryException->getMessage(), $queryException->getCode(), $sql, $params);
2006
        }
2007
2008 33
        if ($this->mysqli_link) {
2009 33
            return $this->queryErrorHandling(\mysqli_error($this->mysqli_link), \mysqli_errno($this->mysqli_link), $sql, $params);
2010
        }
2011
2012
        return false;
2013
    }
2014
2015
    /**
2016
     * @param string $sql
2017
     *
2018
     * @return void
2019
     */
2020 132
    private function queryWarningHandling($sql)
2021
    {
2022 132
        if ($this->mysqli_link && $this->mysqli_link->warning_count > 0) {
2023 4
            $warningTmp = $this->mysqli_link->get_warnings();
2024
            do {
2025 4
                $warningTmpStr = \print_r($warningTmp, true);
2026
                // e.g.: sql mode 'NO_AUTO_CREATE_USER' is deprecated)
2027 4
                if (\strpos($warningTmpStr, 'is deprecated') === false) {
2028 4
                    $this->queryErrorHandling(
2029 4
                        $warningTmp->message,
2030 4
                        $warningTmp->errno,
2031 4
                        $sql,
2032 4
                        false,
2033 4
                        false,
2034 4
                        false
2035
                    );
2036
                }
2037 4
            } while ($warningTmp->next());
2038
        }
2039 132
    }
2040
2041
    /**
2042
     * Error-handling for the sql-query.
2043
     *
2044
     * @param string     $errorMessage
2045
     * @param int        $errorNumber
2046
     * @param string     $sql
2047
     * @param array|bool $sqlParams <p>false if there wasn't any parameter</p>
2048
     * @param bool       $sqlMultiQuery
2049
     * @param bool       $force_exception_after_error
2050
     *
2051
     * @throws QueryException
2052
     * @throws DBGoneAwayException
2053
     *
2054
     * @return false|mixed
2055
     */
2056 40
    private function queryErrorHandling(
2057
        string $errorMessage,
2058
        int $errorNumber,
2059
        string $sql,
2060
        $sqlParams = false,
2061
        bool $sqlMultiQuery = false,
2062
        bool $force_exception_after_error = null
2063
    ) {
2064
        if (
2065 40
            $errorMessage === 'DB server has gone away'
2066
            ||
2067 37
            $errorMessage === 'MySQL server has gone away'
2068
            ||
2069 40
            $errorNumber === 2006
2070
        ) {
2071 3
            static $RECONNECT_COUNTER;
2072
2073
            // exit if we have more then 3 "DB server has gone away"-errors
2074 3
            if ($RECONNECT_COUNTER > 3) {
2075
                $this->debug->mailToAdmin('DB-Fatal-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql, 5);
2076
2077
                throw new DBGoneAwayException($errorMessage);
2078
            }
2079
2080 3
            $this->debug->mailToAdmin('DB-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
2081
2082
            // reconnect
2083 3
            $RECONNECT_COUNTER++;
2084 3
            $this->reconnect(true);
2085
2086
            // re-run the current (non multi) query
2087 3
            if (!$sqlMultiQuery) {
2088 3
                return $this->query($sql, $sqlParams);
2089
            }
2090
2091
            return false;
2092
        }
2093
2094 37
        $this->debug->mailToAdmin('SQL-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
2095
2096
        if (
2097 37
            $force_exception_after_error === null
2098
            &&
2099 37
            $this->in_transaction
2100
        ) {
2101 12
            $force_exception_after_error = false;
2102
        }
2103
2104
        // this query returned an error, we must display it (only for dev) !!!
2105 37
        $this->debug->displayError($errorMessage . '(' . $errorNumber . ') ' . ' | ' . $sql, $force_exception_after_error);
2106
2107 37
        return false;
2108
    }
2109
2110
    /**
2111
     * Quote && Escape e.g. a table name string.
2112
     *
2113
     * @param mixed $str
2114
     *
2115
     * @return string
2116
     */
2117 87
    public function quote_string($str): string
2118
    {
2119 87
        $str = \str_replace(
2120 87
            '`',
2121 87
            '``',
2122 87
            \trim(
2123 87
                (string) $this->escape($str, false),
2124 87
                '`'
2125
            )
2126
        );
2127
2128 87
        return '`' . $str . '`';
2129
    }
2130
2131
    /**
2132
     * Reconnect to the MySQL-Server.
2133
     *
2134
     * @param bool $checkViaPing
2135
     *
2136
     * @return bool
2137
     */
2138 7
    public function reconnect(bool $checkViaPing = false): bool
2139
    {
2140 7
        $ping = false;
2141 7
        if ($checkViaPing) {
2142 6
            $ping = $this->ping();
2143
        }
2144
2145 7
        if (!$ping) {
2146 7
            $this->connected = false;
2147 7
            $this->connect();
2148
        }
2149
2150 7
        return $this->isReady();
2151
    }
2152
2153
    /**
2154
     * Execute a "replace"-query.
2155
     *
2156
     * @param string      $table
2157
     * @param array       $data
2158
     * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2159
     *
2160
     * @throws QueryException
2161
     *
2162
     * @return false|int
2163
     *                   <p>false on error</p>
2164
     */
2165 3
    public function replace(string $table, array $data = [], string $databaseName = null)
2166
    {
2167
        // init
2168 3
        $table = \trim($table);
2169
2170 3
        if ($table === '') {
2171 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
2172
2173 3
            return false;
2174
        }
2175
2176 3
        if (\count($data) === 0) {
2177 3
            $this->debug->displayError('Invalid data for REPLACE, data is empty.', false);
2178
2179 3
            return false;
2180
        }
2181
2182
        // extracting column names
2183 3
        $columns = \array_keys($data);
2184 3
        foreach ($columns as $k => $_key) {
2185 3
            $columns[$k] = $this->quote_string($_key);
2186
        }
2187
2188 3
        $columns = \implode(',', $columns);
2189
2190
        // extracting values
2191 3
        foreach ($data as $k => $_value) {
2192 3
            $data[$k] = $this->secure($_value);
2193
        }
2194 3
        $values = \implode(',', $data);
2195
2196 3
        if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2197
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2198
        }
2199
2200 3
        $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " (${columns}) VALUES (${values})";
2201
2202 3
        $return = $this->query($sql);
2203 3
        \assert(\is_int($return) || $return === false);
2204
2205 3
        return $return;
2206
    }
2207
2208
    /**
2209
     * Rollback in a transaction and end the transaction.
2210
     *
2211
     * @return bool
2212
     *              <p>bool true on success, false otherwise.</p>
2213
     */
2214 12 View Code Duplication
    public function rollback(): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2215
    {
2216 12
        if (!$this->in_transaction) {
2217
            $this->debug->displayError('Error: mysql server is not in transaction!', false);
2218
2219
            return false;
2220
        }
2221
2222
        // init
2223 12
        $return = false;
2224
2225 12
        if ($this->mysqli_link) {
2226 12
            $return = \mysqli_rollback($this->mysqli_link);
2227 12
            \mysqli_autocommit($this->mysqli_link, true);
2228
        } elseif ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
2229
            $this->doctrine_connection->rollBack();
2230
            $this->doctrine_connection->setAutoCommit(true);
2231
2232
            if ($this->doctrine_connection->isAutoCommit()) {
2233
                $return = true;
2234
            } else {
2235
                $return = false;
2236
            }
2237
        }
2238
2239 12
        $this->in_transaction = false;
2240
2241 12
        return $return;
2242
    }
2243
2244
    /**
2245
     * Try to secure a variable, so can you use it in sql-queries.
2246
     *
2247
     * <p>
2248
     * <strong>int:</strong> (also strings that contains only an int-value)<br />
2249
     * 1. parse into "int"
2250
     * </p><br />
2251
     *
2252
     * <p>
2253
     * <strong>float:</strong><br />
2254
     * 1. return "float"
2255
     * </p><br />
2256
     *
2257
     * <p>
2258
     * <strong>string:</strong><br />
2259
     * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
2260
     * 2. trim '<br />
2261
     * 3. escape the string (and remove non utf-8 chars)<br />
2262
     * 4. trim ' again (because we maybe removed some chars)<br />
2263
     * 5. add ' around the new string<br />
2264
     * </p><br />
2265
     *
2266
     * <p>
2267
     * <strong>array:</strong><br />
2268
     * 1. return null
2269
     * </p><br />
2270
     *
2271
     * <p>
2272
     * <strong>object:</strong><br />
2273
     * 1. return false
2274
     * </p><br />
2275
     *
2276
     * <p>
2277
     * <strong>null:</strong><br />
2278
     * 1. return null
2279
     * </p>
2280
     *
2281
     * @param mixed     $var
2282
     * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
2283
     *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
2284
     *                                 <strong>null</strong> => Convert the array into null, every time.
2285
     *
2286
     * @return mixed
2287
     */
2288 88
    public function secure($var, $convert_array = true)
2289
    {
2290 88
        if (\is_array($var)) {
2291 6
            if ($convert_array === null) {
2292
                if ($this->convert_null_to_empty_string) {
2293
                    $var = "''";
2294
                } else {
2295
                    $var = 'NULL';
2296
                }
2297
            } else {
2298 6
                $varCleaned = [];
2299 6
                foreach ((array) $var as $key => $value) {
2300 6
                    $key = $this->escape($key, false, false, $convert_array);
2301 6
                    $value = $this->secure($value);
2302
2303
                    /** @noinspection OffsetOperationsInspection */
2304 6
                    $varCleaned[$key] = $value;
2305
                }
2306
2307 6 View Code Duplication
                if ($convert_array === true) {
2308 6
                    $varCleaned = \implode(',', $varCleaned);
2309
2310 6
                    $var = $varCleaned;
2311
                } else {
2312
                    $var = $varCleaned;
2313
                }
2314
            }
2315
2316 6
            return $var;
2317
        }
2318
2319 88
        if ($var === '') {
2320 6
            return "''";
2321
        }
2322
2323 88
        if ($var === "''") {
2324 3
            return "''";
2325
        }
2326
2327 88
        if ($var === null) {
2328 3
            if ($this->convert_null_to_empty_string) {
2329 3
                return "''";
2330
            }
2331
2332 3
            return 'NULL';
2333
        }
2334
2335 88
        if (\in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
2336 3
            return $var;
2337
        }
2338
2339 88
        if (\is_string($var)) {
2340 77
            $var = \trim($var, "'");
2341
        }
2342
2343 88
        $var = $this->escape($var, false, false, null);
2344
2345 85
        if (\is_string($var)) {
2346 77
            $var = "'" . \trim($var, "'") . "'";
2347
        }
2348
2349 85
        return $var;
2350
    }
2351
2352
    /**
2353
     * Execute a "select"-query.
2354
     *
2355
     * @param string       $table
2356
     * @param array|string $where
2357
     * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2358
     *
2359
     * @throws QueryException
2360
     *
2361
     * @return false|Result
2362
     *                      <p>false on error</p>
2363
     */
2364 63 View Code Duplication
    public function select(
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...
2365
        string $table,
2366
        $where = '1=1',
2367
        string $databaseName = null
2368
    ) {
2369
        // init
2370 63
        $table = \trim($table);
2371
2372 63
        if ($table === '') {
2373 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
2374
2375 3
            return false;
2376
        }
2377
2378 63
        if (\is_string($where)) {
2379 25
            $WHERE = $this->escape($where, false);
2380 42
        } elseif (\is_array($where)) {
2381 42
            $WHERE = $this->_parseArrayPair($where, 'AND');
2382
        } else {
2383 3
            $WHERE = '';
2384
        }
2385
2386 63
        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...
2387
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2388
        }
2389
2390 63
        $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE (${WHERE})";
2391
2392 63
        $return = $this->query($sql);
2393 63
        \assert($return instanceof Result || $return === false);
2394
2395 63
        return $return;
2396
    }
2397
2398
    /**
2399
     * Selects a different database than the one specified on construction.
2400
     *
2401
     * @param string $database <p>Database name to switch to.</p>
2402
     *
2403
     * @return bool
2404
     *              <p>bool true on success, false otherwise.</p>
2405
     */
2406
    public function select_db(string $database): bool
2407
    {
2408
        if (!$this->isReady()) {
2409
            return false;
2410
        }
2411
2412
        if ($this->mysqli_link) {
2413
            return \mysqli_select_db($this->mysqli_link, $database);
2414
        }
2415
2416
        if ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
2417
            $return = $this->query('use :database', ['database' => $database]);
2418
            \assert(\is_bool($return));
2419
2420
            return $return;
2421
        }
2422
2423
        return false;
2424
    }
2425
2426
    /**
2427
     * @param array  $extra_config          <p>
2428
     *                                      'session_to_db' => bool<br>
2429
     *                                      'doctrine'      => \Doctrine\DBAL\Connection<br>
2430
     *                                      'socket'        => string (path)<br>
2431
     *                                      'flags'         => null|int<br>
2432
     *                                      'ssl'           => bool<br>
2433
     *                                      'clientkey'     => string (path)<br>
2434
     *                                      'clientcert'    => string (path)<br>
2435
     *                                      'cacert'        => string (path)<br>
2436
     *                                      </p>
2437
     *
2438
     * @return void
2439
     */
2440 24
    public function setConfigExtra(array $extra_config)
2441
    {
2442 24
        if (isset($extra_config['session_to_db'])) {
2443
            $this->session_to_db = (bool) $extra_config['session_to_db'];
2444
        }
2445
2446 24
        if (isset($extra_config['doctrine'])) {
2447
            if ($extra_config['doctrine'] instanceof \Doctrine\DBAL\Connection) {
2448
                $this->doctrine_connection = $extra_config['doctrine'];
2449
            } else {
2450
                throw new DBConnectException('Error "doctrine"-connection is not valid');
2451
            }
2452
        }
2453
2454 24
        if (isset($extra_config['socket'])) {
2455
            $this->socket = $extra_config['socket'];
2456
        }
2457
2458 24
        if (isset($extra_config['flags'])) {
2459 1
            $this->flags = $extra_config['flags'];
2460
        }
2461
2462 24
        if (isset($extra_config['ssl'])) {
2463
            $this->ssl = $extra_config['ssl'];
2464
        }
2465
2466 24
        if (isset($extra_config['clientkey'])) {
2467
            $this->clientkey = $extra_config['clientkey'];
2468
        }
2469
2470 24
        if (isset($extra_config['clientcert'])) {
2471
            $this->clientcert = $extra_config['clientcert'];
2472
        }
2473
2474 24
        if (isset($extra_config['cacert'])) {
2475
            $this->cacert = $extra_config['cacert'];
2476
        }
2477 24
    }
2478
2479
    /**
2480
     * Set the current charset.
2481
     *
2482
     * @param string $charset
2483
     *
2484
     * @return bool
2485
     */
2486 15
    public function set_charset(string $charset): bool
2487
    {
2488 15
        $charsetLower = \strtolower($charset);
2489 15
        if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
2490 9
            $charset = 'utf8';
2491
        }
2492 15
        if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this)) {
2493 9
            $charset = 'utf8mb4';
2494
        }
2495
2496 15
        $this->charset = $charset;
2497
2498
        if (
2499 15
            $this->mysqli_link
2500
            &&
2501 15
            $this->mysqli_link instanceof \mysqli
2502
        ) {
2503 15
            $return = \mysqli_set_charset($this->mysqli_link, $charset);
2504
2505
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
2506 15
            @\mysqli_query($this->mysqli_link, 'SET CHARACTER SET ' . $charset);
2507
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
2508 15
            @\mysqli_query($this->mysqli_link, "SET NAMES '" . $charset . "'");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2509
        } elseif ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
2510
            $doctrineWrappedConnection = $this->getDoctrinePDOConnection();
2511
            if (!$doctrineWrappedConnection instanceof \Doctrine\DBAL\Connection) {
2512
                return false;
2513
            }
2514
2515
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
2516
            @$doctrineWrappedConnection->exec('SET CHARACTER SET ' . $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...
2517
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
2518
            @$doctrineWrappedConnection->exec("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...
2519
2520
            $return = true;
2521
        } else {
2522
            $return = false;
2523
        }
2524
2525 15
        return $return;
2526
    }
2527
2528
    /**
2529
     * Set the option to convert null to "''" (empty string).
2530
     *
2531
     * Used in secure() => select(), insert(), update(), delete()
2532
     *
2533
     * @deprecated It's not recommended to convert NULL into an empty string!
2534
     *
2535
     * @param bool $bool
2536
     *
2537
     * @return self
2538
     */
2539 3
    public function set_convert_null_to_empty_string(bool $bool): self
2540
    {
2541 3
        $this->convert_null_to_empty_string = $bool;
2542
2543 3
        return $this;
2544
    }
2545
2546
    /**
2547
     * Enables or disables internal report functions
2548
     *
2549
     * @see http://php.net/manual/en/function.mysqli-report.php
2550
     *
2551
     * @param int $flags <p>
2552
     *                   <table>
2553
     *                   Supported flags
2554
     *                   <tr valign="top">
2555
     *                   <td>Name</td>
2556
     *                   <td>Description</td>
2557
     *                   </tr>
2558
     *                   <tr valign="top">
2559
     *                   <td><b>MYSQLI_REPORT_OFF</b></td>
2560
     *                   <td>Turns reporting off</td>
2561
     *                   </tr>
2562
     *                   <tr valign="top">
2563
     *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
2564
     *                   <td>Report errors from mysqli function calls</td>
2565
     *                   </tr>
2566
     *                   <tr valign="top">
2567
     *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
2568
     *                   <td>
2569
     *                   Throw <b>mysqli_sql_exception</b> for errors
2570
     *                   instead of warnings
2571
     *                   </td>
2572
     *                   </tr>
2573
     *                   <tr valign="top">
2574
     *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
2575
     *                   <td>Report if no index or bad index was used in a query</td>
2576
     *                   </tr>
2577
     *                   <tr valign="top">
2578
     *                   <td><b>MYSQLI_REPORT_ALL</b></td>
2579
     *                   <td>Set all options (report all)</td>
2580
     *                   </tr>
2581
     *                   </table>
2582
     *                   </p>
2583
     *
2584
     * @return bool
2585
     */
2586
    public function set_mysqli_report(int $flags): bool
2587
    {
2588
        if (
2589
            $this->mysqli_link
2590
            &&
2591
            $this->mysqli_link instanceof \mysqli
2592
        ) {
2593
            return \mysqli_report($flags);
2594
        }
2595
2596
        return false;
2597
    }
2598
2599
    /**
2600
     * Show config errors by throw exceptions.
2601
     *
2602
     * @throws \InvalidArgumentException
2603
     *
2604
     * @return bool
2605
     */
2606 24
    public function showConfigError(): bool
2607
    {
2608
        // check if a doctrine connection is already open, first
2609
        if (
2610 24
            $this->doctrine_connection
2611
            &&
2612 24
            $this->doctrine_connection->isConnected()
2613
        ) {
2614
            return true;
2615
        }
2616
2617
        if (
2618 24
            !$this->hostname
2619
            ||
2620 21
            !$this->username
2621
            ||
2622 24
            !$this->database
2623
        ) {
2624 9
            if (!$this->hostname) {
2625 3
                throw new \InvalidArgumentException('no-sql-hostname');
2626
            }
2627
2628 6
            if (!$this->username) {
2629 3
                throw new \InvalidArgumentException('no-sql-username');
2630
            }
2631
2632 3
            if (!$this->database) {
2633 3
                throw new \InvalidArgumentException('no-sql-database');
2634
            }
2635
2636
            return false;
2637
        }
2638
2639 15
        return true;
2640
    }
2641
2642
    /**
2643
     * alias: "beginTransaction()"
2644
     */
2645 3
    public function startTransaction(): bool
2646
    {
2647 3
        return $this->beginTransaction();
2648
    }
2649
2650
    /**
2651
     * Determine if database table exists
2652
     *
2653
     * @param string $table
2654
     *
2655
     * @return bool
2656
     */
2657 3
    public function table_exists(string $table): bool
2658
    {
2659 3
        $check = $this->query('SELECT 1 FROM ' . $this->quote_string($table));
2660
2661 3
        return $check !== false
2662
               &&
2663 3
               $check instanceof Result
2664
               &&
2665 3
               $check->num_rows > 0;
2666
    }
2667
2668
    /**
2669
     * Execute a callback inside a transaction.
2670
     *
2671
     * @param \Closure $callback <p>The callback to run inside the transaction, if it's throws an "Exception" or if it's
2672
     *                           returns "false", all SQL-statements in the callback will be rollbacked.</p>
2673
     *
2674
     * @return bool
2675
     *              <p>bool true on success, false otherwise.</p>
2676
     */
2677 3
    public function transact($callback): bool
2678
    {
2679
        try {
2680 3
            $beginTransaction = $this->beginTransaction();
2681 3
            if (!$beginTransaction) {
2682 3
                $this->debug->displayError('Error: transact -> can not start transaction!', false);
2683
2684 3
                return false;
2685
            }
2686
2687 3
            $result = $callback($this);
2688 3
            if ($result === false) {
2689
                /** @noinspection ThrowRawExceptionInspection */
2690 3
                throw new \Exception('call_user_func [' . \print_r($callback, true) . '] === false');
2691
            }
2692
2693 3
            return $this->commit();
2694 3
        } catch (\Exception $e) {
2695 3
            $this->rollback();
2696
2697 3
            return false;
2698
        }
2699
    }
2700
2701
    /**
2702
     * Execute a "update"-query.
2703
     *
2704
     * @param string       $table
2705
     * @param array        $data
2706
     * @param array|string $where
2707
     * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2708
     *
2709
     * @throws QueryException
2710
     *
2711
     * @return false|int
2712
     *                   <p>false on error</p>
2713
     */
2714 21
    public function update(
2715
        string $table,
2716
        array $data = [],
2717
        $where = '1=1',
2718
        string $databaseName = null
2719
    ) {
2720
        // init
2721 21
        $table = \trim($table);
2722
2723 21
        if ($table === '') {
2724 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
2725
2726 3
            return false;
2727
        }
2728
2729 21
        if (\count($data) === 0) {
2730 6
            $this->debug->displayError('Invalid data for UPDATE, data is empty.', false);
2731
2732 6
            return false;
2733
        }
2734
2735
        // DEBUG
2736
        //var_dump($data);
2737
2738 21
        $SET = $this->_parseArrayPair($data);
2739
2740
        // DEBUG
2741
        //var_dump($SET);
2742
2743 21
        if (\is_string($where)) {
2744 6
            $WHERE = $this->escape($where, false);
2745 18
        } elseif (\is_array($where)) {
2746 15
            $WHERE = $this->_parseArrayPair($where, 'AND');
2747
        } else {
2748 3
            $WHERE = '';
2749
        }
2750
2751 21
        if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2752
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2753
        }
2754
2755 21
        $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET ${SET} WHERE (${WHERE})";
2756
2757 21
        $return = $this->query($sql);
2758 21
        \assert(\is_int($return) || $return === false);
2759
2760 21
        return $return;
2761
    }
2762
}
2763