Completed
Push — master ( fd537c...09282c )
by Lars
01:48
created

DB   F

Complexity

Total Complexity 372

Size/Duplication

Total Lines 2766
Duplicated Lines 7.7 %

Coupling/Cohesion

Components 2
Dependencies 13

Test Coverage

Coverage 75.54%

Importance

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

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
2033
                            ||
2034
                            \strpos($query, 'TABLE IF NOT EXISTS') !== false
2035
                        )
2036
                        &&
2037
                        (
2038
                            \strpos($warningTmpStr, 'Unknown table') !== false
2039
                            ||
2040 4
                            \strpos($warningTmpStr, 'already exists') !== false
2041
                        )
2042
                    )
2043
                ) {
2044
                    // no warning
2045
                } else {
2046 4
                    $this->queryErrorHandling(
2047 4
                        $warningTmp->message,
2048 4
                        $warningTmp->errno,
2049 4
                        $sql,
2050 4
                        false,
2051 4
                        false,
2052 4
                        false
2053
                    );
2054
                }
2055 4
            } while ($warningTmp->next());
2056
        }
2057 132
    }
2058
2059
    /**
2060
     * Error-handling for the sql-query.
2061
     *
2062
     * @param string     $errorMessage
2063
     * @param int        $errorNumber
2064
     * @param string     $sql
2065
     * @param array|bool $sqlParams <p>false if there wasn't any parameter</p>
2066
     * @param bool       $sqlMultiQuery
2067
     * @param bool       $force_exception_after_error
2068
     *
2069
     * @throws QueryException
2070
     * @throws DBGoneAwayException
2071
     *
2072
     * @return false|mixed
2073
     */
2074 40
    private function queryErrorHandling(
2075
        string $errorMessage,
2076
        int $errorNumber,
2077
        string $sql,
2078
        $sqlParams = false,
2079
        bool $sqlMultiQuery = false,
2080
        bool $force_exception_after_error = null
2081
    ) {
2082
        if (
2083 40
            $errorMessage === 'DB server has gone away'
2084
            ||
2085 37
            $errorMessage === 'MySQL server has gone away'
2086
            ||
2087 40
            $errorNumber === 2006
2088
        ) {
2089 3
            static $RECONNECT_COUNTER;
2090
2091
            // exit if we have more then 3 "DB server has gone away"-errors
2092 3
            if ($RECONNECT_COUNTER > 3) {
2093
                $this->debug->mailToAdmin('DB-Fatal-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql, 5);
2094
2095
                throw new DBGoneAwayException($errorMessage);
2096
            }
2097
2098 3
            $this->debug->mailToAdmin('DB-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
2099
2100
            // reconnect
2101 3
            $RECONNECT_COUNTER++;
2102 3
            $this->reconnect(true);
2103
2104
            // re-run the current (non multi) query
2105 3
            if (!$sqlMultiQuery) {
2106 3
                return $this->query($sql, $sqlParams);
2107
            }
2108
2109
            return false;
2110
        }
2111
2112 37
        $this->debug->mailToAdmin('SQL-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
2113
2114
        if (
2115 37
            $force_exception_after_error === null
2116
            &&
2117 37
            $this->in_transaction
2118
        ) {
2119 12
            $force_exception_after_error = false;
2120
        }
2121
2122
        // this query returned an error, we must display it (only for dev) !!!
2123 37
        $this->debug->displayError($errorMessage . '(' . $errorNumber . ') ' . ' | ' . $sql, $force_exception_after_error);
2124
2125 37
        return false;
2126
    }
2127
2128
    /**
2129
     * Quote && Escape e.g. a table name string.
2130
     *
2131
     * @param mixed $str
2132
     *
2133
     * @return string
2134
     */
2135 87
    public function quote_string($str): string
2136
    {
2137 87
        $str = \str_replace(
2138 87
            '`',
2139 87
            '``',
2140 87
            \trim(
2141 87
                (string) $this->escape($str, false),
2142 87
                '`'
2143
            )
2144
        );
2145
2146 87
        return '`' . $str . '`';
2147
    }
2148
2149
    /**
2150
     * Reconnect to the MySQL-Server.
2151
     *
2152
     * @param bool $checkViaPing
2153
     *
2154
     * @return bool
2155
     */
2156 7
    public function reconnect(bool $checkViaPing = false): bool
2157
    {
2158 7
        $ping = false;
2159 7
        if ($checkViaPing) {
2160 6
            $ping = $this->ping();
2161
        }
2162
2163 7
        if (!$ping) {
2164 7
            $this->connected = false;
2165 7
            $this->connect();
2166
        }
2167
2168 7
        return $this->isReady();
2169
    }
2170
2171
    /**
2172
     * Execute a "replace"-query.
2173
     *
2174
     * @param string      $table
2175
     * @param array       $data
2176
     * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2177
     *
2178
     * @throws QueryException
2179
     *
2180
     * @return false|int
2181
     *                   <p>false on error</p>
2182
     */
2183 3
    public function replace(string $table, array $data = [], string $databaseName = null)
2184
    {
2185
        // init
2186 3
        $table = \trim($table);
2187
2188 3
        if ($table === '') {
2189 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
2190
2191 3
            return false;
2192
        }
2193
2194 3
        if (\count($data) === 0) {
2195 3
            $this->debug->displayError('Invalid data for REPLACE, data is empty.', false);
2196
2197 3
            return false;
2198
        }
2199
2200
        // extracting column names
2201 3
        $columns = \array_keys($data);
2202 3
        foreach ($columns as $k => $_key) {
2203 3
            $columns[$k] = $this->quote_string($_key);
2204
        }
2205
2206 3
        $columns = \implode(',', $columns);
2207
2208
        // extracting values
2209 3
        foreach ($data as $k => $_value) {
2210 3
            $data[$k] = $this->secure($_value);
2211
        }
2212 3
        $values = \implode(',', $data);
2213
2214 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...
2215
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2216
        }
2217
2218 3
        $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " (${columns}) VALUES (${values})";
2219
2220 3
        $return = $this->query($sql);
2221 3
        \assert(\is_int($return) || $return === false);
2222
2223 3
        return $return;
2224
    }
2225
2226
    /**
2227
     * Rollback in a transaction and end the transaction.
2228
     *
2229
     * @return bool
2230
     *              <p>bool true on success, false otherwise.</p>
2231
     */
2232 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...
2233
    {
2234 12
        if (!$this->in_transaction) {
2235
            $this->debug->displayError('Error: mysql server is not in transaction!', false);
2236
2237
            return false;
2238
        }
2239
2240
        // init
2241 12
        $return = false;
2242
2243 12
        if ($this->mysqli_link) {
2244 12
            $return = \mysqli_rollback($this->mysqli_link);
2245 12
            \mysqli_autocommit($this->mysqli_link, true);
2246
        } elseif ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
2247
            $this->doctrine_connection->rollBack();
2248
            $this->doctrine_connection->setAutoCommit(true);
2249
2250
            if ($this->doctrine_connection->isAutoCommit()) {
2251
                $return = true;
2252
            } else {
2253
                $return = false;
2254
            }
2255
        }
2256
2257 12
        $this->in_transaction = false;
2258
2259 12
        return $return;
2260
    }
2261
2262
    /**
2263
     * Try to secure a variable, so can you use it in sql-queries.
2264
     *
2265
     * <p>
2266
     * <strong>int:</strong> (also strings that contains only an int-value)<br />
2267
     * 1. parse into "int"
2268
     * </p><br />
2269
     *
2270
     * <p>
2271
     * <strong>float:</strong><br />
2272
     * 1. return "float"
2273
     * </p><br />
2274
     *
2275
     * <p>
2276
     * <strong>string:</strong><br />
2277
     * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
2278
     * 2. trim '<br />
2279
     * 3. escape the string (and remove non utf-8 chars)<br />
2280
     * 4. trim ' again (because we maybe removed some chars)<br />
2281
     * 5. add ' around the new string<br />
2282
     * </p><br />
2283
     *
2284
     * <p>
2285
     * <strong>array:</strong><br />
2286
     * 1. return null
2287
     * </p><br />
2288
     *
2289
     * <p>
2290
     * <strong>object:</strong><br />
2291
     * 1. return false
2292
     * </p><br />
2293
     *
2294
     * <p>
2295
     * <strong>null:</strong><br />
2296
     * 1. return null
2297
     * </p>
2298
     *
2299
     * @param mixed     $var
2300
     * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
2301
     *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
2302
     *                                 <strong>null</strong> => Convert the array into null, every time.
2303
     *
2304
     * @return mixed
2305
     */
2306 88
    public function secure($var, $convert_array = true)
2307
    {
2308 88
        if (\is_array($var)) {
2309 6
            if ($convert_array === null) {
2310
                if ($this->convert_null_to_empty_string) {
2311
                    $var = "''";
2312
                } else {
2313
                    $var = 'NULL';
2314
                }
2315
            } else {
2316 6
                $varCleaned = [];
2317 6
                foreach ((array) $var as $key => $value) {
2318 6
                    $key = $this->escape($key, false, false, $convert_array);
2319 6
                    $value = $this->secure($value);
2320
2321
                    /** @noinspection OffsetOperationsInspection */
2322 6
                    $varCleaned[$key] = $value;
2323
                }
2324
2325 6 View Code Duplication
                if ($convert_array === true) {
2326 6
                    $varCleaned = \implode(',', $varCleaned);
2327
2328 6
                    $var = $varCleaned;
2329
                } else {
2330
                    $var = $varCleaned;
2331
                }
2332
            }
2333
2334 6
            return $var;
2335
        }
2336
2337 88
        if ($var === '') {
2338 6
            return "''";
2339
        }
2340
2341 88
        if ($var === "''") {
2342 3
            return "''";
2343
        }
2344
2345 88
        if ($var === null) {
2346 3
            if ($this->convert_null_to_empty_string) {
2347 3
                return "''";
2348
            }
2349
2350 3
            return 'NULL';
2351
        }
2352
2353 88
        if (\in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
2354 3
            return $var;
2355
        }
2356
2357 88
        if (\is_string($var)) {
2358 77
            $var = \trim($var, "'");
2359
        }
2360
2361 88
        $var = $this->escape($var, false, false, null);
2362
2363 85
        if (\is_string($var)) {
2364 77
            $var = "'" . \trim($var, "'") . "'";
2365
        }
2366
2367 85
        return $var;
2368
    }
2369
2370
    /**
2371
     * Execute a "select"-query.
2372
     *
2373
     * @param string       $table
2374
     * @param array|string $where
2375
     * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2376
     *
2377
     * @throws QueryException
2378
     *
2379
     * @return false|Result
2380
     *                      <p>false on error</p>
2381
     */
2382 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...
2383
        string $table,
2384
        $where = '1=1',
2385
        string $databaseName = null
2386
    ) {
2387
        // init
2388 63
        $table = \trim($table);
2389
2390 63
        if ($table === '') {
2391 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
2392
2393 3
            return false;
2394
        }
2395
2396 63
        if (\is_string($where)) {
2397 25
            $WHERE = $this->escape($where, false);
2398 42
        } elseif (\is_array($where)) {
2399 42
            $WHERE = $this->_parseArrayPair($where, 'AND');
2400
        } else {
2401 3
            $WHERE = '';
2402
        }
2403
2404 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...
2405
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2406
        }
2407
2408 63
        $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE (${WHERE})";
2409
2410 63
        $return = $this->query($sql);
2411 63
        \assert($return instanceof Result || $return === false);
2412
2413 63
        return $return;
2414
    }
2415
2416
    /**
2417
     * Selects a different database than the one specified on construction.
2418
     *
2419
     * @param string $database <p>Database name to switch to.</p>
2420
     *
2421
     * @return bool
2422
     *              <p>bool true on success, false otherwise.</p>
2423
     */
2424
    public function select_db(string $database): bool
2425
    {
2426
        if (!$this->isReady()) {
2427
            return false;
2428
        }
2429
2430
        if ($this->mysqli_link) {
2431
            return \mysqli_select_db($this->mysqli_link, $database);
2432
        }
2433
2434
        if ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
2435
            $return = $this->query('use :database', ['database' => $database]);
2436
            \assert(\is_bool($return));
2437
2438
            return $return;
2439
        }
2440
2441
        return false;
2442
    }
2443
2444
    /**
2445
     * @param array  $extra_config          <p>
2446
     *                                      'session_to_db' => bool<br>
2447
     *                                      'doctrine'      => \Doctrine\DBAL\Connection<br>
2448
     *                                      'socket'        => string (path)<br>
2449
     *                                      'flags'         => null|int<br>
2450
     *                                      'ssl'           => bool<br>
2451
     *                                      'clientkey'     => string (path)<br>
2452
     *                                      'clientcert'    => string (path)<br>
2453
     *                                      'cacert'        => string (path)<br>
2454
     *                                      </p>
2455
     *
2456
     * @return void
2457
     */
2458 24
    public function setConfigExtra(array $extra_config)
2459
    {
2460 24
        if (isset($extra_config['session_to_db'])) {
2461
            $this->session_to_db = (bool) $extra_config['session_to_db'];
2462
        }
2463
2464 24
        if (isset($extra_config['doctrine'])) {
2465
            if ($extra_config['doctrine'] instanceof \Doctrine\DBAL\Connection) {
2466
                $this->doctrine_connection = $extra_config['doctrine'];
2467
            } else {
2468
                throw new DBConnectException('Error "doctrine"-connection is not valid');
2469
            }
2470
        }
2471
2472 24
        if (isset($extra_config['socket'])) {
2473
            $this->socket = $extra_config['socket'];
2474
        }
2475
2476 24
        if (isset($extra_config['flags'])) {
2477 1
            $this->flags = $extra_config['flags'];
2478
        }
2479
2480 24
        if (isset($extra_config['ssl'])) {
2481
            $this->ssl = $extra_config['ssl'];
2482
        }
2483
2484 24
        if (isset($extra_config['clientkey'])) {
2485
            $this->clientkey = $extra_config['clientkey'];
2486
        }
2487
2488 24
        if (isset($extra_config['clientcert'])) {
2489
            $this->clientcert = $extra_config['clientcert'];
2490
        }
2491
2492 24
        if (isset($extra_config['cacert'])) {
2493
            $this->cacert = $extra_config['cacert'];
2494
        }
2495 24
    }
2496
2497
    /**
2498
     * Set the current charset.
2499
     *
2500
     * @param string $charset
2501
     *
2502
     * @return bool
2503
     */
2504 15
    public function set_charset(string $charset): bool
2505
    {
2506 15
        $charsetLower = \strtolower($charset);
2507 15
        if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
2508 9
            $charset = 'utf8';
2509
        }
2510 15
        if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this)) {
2511 9
            $charset = 'utf8mb4';
2512
        }
2513
2514 15
        $this->charset = $charset;
2515
2516
        if (
2517 15
            $this->mysqli_link
2518
            &&
2519 15
            $this->mysqli_link instanceof \mysqli
2520
        ) {
2521 15
            $return = \mysqli_set_charset($this->mysqli_link, $charset);
2522
2523
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
2524 15
            @\mysqli_query($this->mysqli_link, 'SET CHARACTER SET ' . $charset);
2525
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
2526 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...
2527
        } elseif ($this->doctrine_connection && $this->isDoctrinePDOConnection()) {
2528
            $doctrineWrappedConnection = $this->getDoctrinePDOConnection();
2529
            if (!$doctrineWrappedConnection instanceof \Doctrine\DBAL\Connection) {
2530
                return false;
2531
            }
2532
2533
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
2534
            @$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...
2535
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
2536
            @$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...
2537
2538
            $return = true;
2539
        } else {
2540
            $return = false;
2541
        }
2542
2543 15
        return $return;
2544
    }
2545
2546
    /**
2547
     * Set the option to convert null to "''" (empty string).
2548
     *
2549
     * Used in secure() => select(), insert(), update(), delete()
2550
     *
2551
     * @deprecated It's not recommended to convert NULL into an empty string!
2552
     *
2553
     * @param bool $bool
2554
     *
2555
     * @return self
2556
     */
2557 3
    public function set_convert_null_to_empty_string(bool $bool): self
2558
    {
2559 3
        $this->convert_null_to_empty_string = $bool;
2560
2561 3
        return $this;
2562
    }
2563
2564
    /**
2565
     * Enables or disables internal report functions
2566
     *
2567
     * @see http://php.net/manual/en/function.mysqli-report.php
2568
     *
2569
     * @param int $flags <p>
2570
     *                   <table>
2571
     *                   Supported flags
2572
     *                   <tr valign="top">
2573
     *                   <td>Name</td>
2574
     *                   <td>Description</td>
2575
     *                   </tr>
2576
     *                   <tr valign="top">
2577
     *                   <td><b>MYSQLI_REPORT_OFF</b></td>
2578
     *                   <td>Turns reporting off</td>
2579
     *                   </tr>
2580
     *                   <tr valign="top">
2581
     *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
2582
     *                   <td>Report errors from mysqli function calls</td>
2583
     *                   </tr>
2584
     *                   <tr valign="top">
2585
     *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
2586
     *                   <td>
2587
     *                   Throw <b>mysqli_sql_exception</b> for errors
2588
     *                   instead of warnings
2589
     *                   </td>
2590
     *                   </tr>
2591
     *                   <tr valign="top">
2592
     *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
2593
     *                   <td>Report if no index or bad index was used in a query</td>
2594
     *                   </tr>
2595
     *                   <tr valign="top">
2596
     *                   <td><b>MYSQLI_REPORT_ALL</b></td>
2597
     *                   <td>Set all options (report all)</td>
2598
     *                   </tr>
2599
     *                   </table>
2600
     *                   </p>
2601
     *
2602
     * @return bool
2603
     */
2604
    public function set_mysqli_report(int $flags): bool
2605
    {
2606
        if (
2607
            $this->mysqli_link
2608
            &&
2609
            $this->mysqli_link instanceof \mysqli
2610
        ) {
2611
            return \mysqli_report($flags);
2612
        }
2613
2614
        return false;
2615
    }
2616
2617
    /**
2618
     * Show config errors by throw exceptions.
2619
     *
2620
     * @throws \InvalidArgumentException
2621
     *
2622
     * @return bool
2623
     */
2624 24
    public function showConfigError(): bool
2625
    {
2626
        // check if a doctrine connection is already open, first
2627
        if (
2628 24
            $this->doctrine_connection
2629
            &&
2630 24
            $this->doctrine_connection->isConnected()
2631
        ) {
2632
            return true;
2633
        }
2634
2635
        if (
2636 24
            !$this->hostname
2637
            ||
2638 21
            !$this->username
2639
            ||
2640 24
            !$this->database
2641
        ) {
2642 9
            if (!$this->hostname) {
2643 3
                throw new \InvalidArgumentException('no-sql-hostname');
2644
            }
2645
2646 6
            if (!$this->username) {
2647 3
                throw new \InvalidArgumentException('no-sql-username');
2648
            }
2649
2650 3
            if (!$this->database) {
2651 3
                throw new \InvalidArgumentException('no-sql-database');
2652
            }
2653
2654
            return false;
2655
        }
2656
2657 15
        return true;
2658
    }
2659
2660
    /**
2661
     * alias: "beginTransaction()"
2662
     */
2663 3
    public function startTransaction(): bool
2664
    {
2665 3
        return $this->beginTransaction();
2666
    }
2667
2668
    /**
2669
     * Determine if database table exists
2670
     *
2671
     * @param string $table
2672
     *
2673
     * @return bool
2674
     */
2675 3
    public function table_exists(string $table): bool
2676
    {
2677 3
        $check = $this->query('SELECT 1 FROM ' . $this->quote_string($table));
2678
2679 3
        return $check !== false
2680
               &&
2681 3
               $check instanceof Result
2682
               &&
2683 3
               $check->num_rows > 0;
2684
    }
2685
2686
    /**
2687
     * Execute a callback inside a transaction.
2688
     *
2689
     * @param \Closure $callback <p>The callback to run inside the transaction, if it's throws an "Exception" or if it's
2690
     *                           returns "false", all SQL-statements in the callback will be rollbacked.</p>
2691
     *
2692
     * @return bool
2693
     *              <p>bool true on success, false otherwise.</p>
2694
     */
2695 3
    public function transact($callback): bool
2696
    {
2697
        try {
2698 3
            $beginTransaction = $this->beginTransaction();
2699 3
            if (!$beginTransaction) {
2700 3
                $this->debug->displayError('Error: transact -> can not start transaction!', false);
2701
2702 3
                return false;
2703
            }
2704
2705 3
            $result = $callback($this);
2706 3
            if ($result === false) {
2707
                /** @noinspection ThrowRawExceptionInspection */
2708 3
                throw new \Exception('call_user_func [' . \print_r($callback, true) . '] === false');
2709
            }
2710
2711 3
            return $this->commit();
2712 3
        } catch (\Exception $e) {
2713 3
            $this->rollback();
2714
2715 3
            return false;
2716
        }
2717
    }
2718
2719
    /**
2720
     * Execute a "update"-query.
2721
     *
2722
     * @param string       $table
2723
     * @param array        $data
2724
     * @param array|string $where
2725
     * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2726
     *
2727
     * @throws QueryException
2728
     *
2729
     * @return false|int
2730
     *                   <p>false on error</p>
2731
     */
2732 21
    public function update(
2733
        string $table,
2734
        array $data = [],
2735
        $where = '1=1',
2736
        string $databaseName = null
2737
    ) {
2738
        // init
2739 21
        $table = \trim($table);
2740
2741 21
        if ($table === '') {
2742 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
2743
2744 3
            return false;
2745
        }
2746
2747 21
        if (\count($data) === 0) {
2748 6
            $this->debug->displayError('Invalid data for UPDATE, data is empty.', false);
2749
2750 6
            return false;
2751
        }
2752
2753
        // DEBUG
2754
        //var_dump($data);
2755
2756 21
        $SET = $this->_parseArrayPair($data);
2757
2758
        // DEBUG
2759
        //var_dump($SET);
2760
2761 21
        if (\is_string($where)) {
2762 6
            $WHERE = $this->escape($where, false);
2763 18
        } elseif (\is_array($where)) {
2764 15
            $WHERE = $this->_parseArrayPair($where, 'AND');
2765
        } else {
2766 3
            $WHERE = '';
2767
        }
2768
2769 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...
2770
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2771
        }
2772
2773 21
        $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET ${SET} WHERE (${WHERE})";
2774
2775 21
        $return = $this->query($sql);
2776 21
        \assert(\is_int($return) || $return === false);
2777
2778 21
        return $return;
2779
    }
2780
}
2781