Completed
Push — master ( 6e438c...c2009b )
by Lars
02:10
created

DB::connect()   D

Complexity

Conditions 15
Paths 55

Size

Total Lines 118

Duplication

Lines 33
Ratio 27.97 %

Code Coverage

Tests 28
CRAP Score 57.9533

Importance

Changes 0
Metric Value
dl 33
loc 118
ccs 28
cts 66
cp 0.4242
rs 4.7333
c 0
b 0
f 0
cc 15
nc 55
nop 0
crap 57.9533

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
647
            $this->in_transaction = false;
648
        }
649
650 18
        return $return;
651
    }
652
653
    /**
654
     * Clear the errors in "_debug->_errors".
655
     *
656
     * @return bool
657
     */
658 18
    public function clearErrors(): bool
659
    {
660 18
        return $this->debug->clearErrors();
661
    }
662
663
    /**
664
     * Closes a previously opened database connection.
665
     *
666
     * @return bool
667
     *              Will return "true", if the connection was closed,
668
     *              otherwise (e.g. if the connection was already closed) "false".
669
     */
670 6
    public function close(): bool
671
    {
672 6
        $this->connected = false;
673
674
        if (
675 6
            $this->doctrine_connection
676
            &&
677 6
            $this->doctrine_connection instanceof \Doctrine\DBAL\Connection
678
        ) {
679
            $connectedBefore = $this->doctrine_connection->isConnected();
680
681
            $this->doctrine_connection->close();
682
683
            $this->mysqli_link = null;
684
685
            if ($connectedBefore === true) {
686
                return !$this->doctrine_connection->isConnected();
687
            }
688
689
            return false;
690
        }
691
692
        if (
693 6
            $this->mysqli_link
694
            &&
695 6
            $this->mysqli_link instanceof \mysqli
696
        ) {
697 6
            $result = \mysqli_close($this->mysqli_link);
698 6
            $this->mysqli_link = null;
699
700 6
            return $result;
701
        }
702
703 3
        $this->mysqli_link = null;
704
705 3
        return false;
706
    }
707
708
    /**
709
     * Commits the current transaction and end the transaction.
710
     *
711
     * @return bool <p>bool true on success, false otherwise.</p>
712
     */
713 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...
714
    {
715 9
        if ($this->in_transaction === false) {
716
            $this->debug->displayError('Error: mysql server is not in transaction!', false);
717
718
            return false;
719
        }
720
721 9
        if ($this->mysqli_link) {
722 9
            $return = \mysqli_commit($this->mysqli_link);
723 9
            \mysqli_autocommit($this->mysqli_link, true);
724
        } elseif ($this->isDoctrinePDOConnection() === true) {
725
            $this->doctrine_connection->commit();
726
            $this->doctrine_connection->setAutoCommit(true);
727
728
            if ($this->doctrine_connection->isAutoCommit() === true) {
729
                $return = true;
730
            } else {
731
                $return = false;
732
            }
733
        }
734
735 9
        $this->in_transaction = false;
736
737 9
        return $return;
0 ignored issues
show
Bug introduced by
The variable $return does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
738
    }
739
740
    /**
741
     * Open a new connection to the MySQL server.
742
     *
743
     * @throws DBConnectException
744
     *
745
     * @return bool
746
     */
747 20
    public function connect(): bool
748
    {
749 20
        if ($this->isReady()) {
750 3
            return true;
751
        }
752
753 20
        if ($this->doctrine_connection) {
754
            $this->doctrine_connection->connect();
755
756
            $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
757
758 View Code Duplication
            if ($this->isDoctrineMySQLiConnection() === true) {
759
                /* @var $doctrineWrappedConnection \Doctrine\DBAL\Driver\Mysqli\MysqliConnection */
760
761
                $this->mysqli_link = $doctrineWrappedConnection->getWrappedResourceHandle();
762
763
                $this->connected = $this->doctrine_connection->isConnected();
764
765
                if (!$this->connected) {
766
                    $error = 'Error connecting to mysql server: ' . $this->doctrine_connection->errorInfo();
767
                    $this->debug->displayError($error, false);
768
769
                    throw new DBConnectException($error, 101);
770
                }
771
772
                $this->set_charset($this->charset);
773
774
                return $this->isReady();
775
            }
776
777 View Code Duplication
            if ($this->isDoctrinePDOConnection() === true) {
778
                $this->mysqli_link = null;
779
780
                $this->connected = $this->doctrine_connection->isConnected();
781
782
                if (!$this->connected) {
783
                    $error = 'Error connecting to mysql server: ' . $this->doctrine_connection->errorInfo();
784
                    $this->debug->displayError($error, false);
785
786
                    throw new DBConnectException($error, 101);
787
                }
788
789
                $this->set_charset($this->charset);
790
791
                return $this->isReady();
792
            }
793
        }
794
795 20
        $flags = null;
796
797 20
        \mysqli_report(\MYSQLI_REPORT_STRICT);
798
799
        try {
800 20
            $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...
801
802 20
            if (Helper::isMysqlndIsUsed() === true) {
803 20
                \mysqli_options($this->mysqli_link, \MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
804
            }
805
806 20
            if ($this->ssl === true) {
807
                if (empty($this->clientcert)) {
808
                    throw new DBConnectException('Error connecting to mysql server: clientcert not defined');
809
                }
810
811
                if (empty($this->clientkey)) {
812
                    throw new DBConnectException('Error connecting to mysql server: clientkey not defined');
813
                }
814
815
                if (empty($this->cacert)) {
816
                    throw new DBConnectException('Error connecting to mysql server: cacert not defined');
817
                }
818
819
                \mysqli_options($this->mysqli_link, \MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
820
821
                /** @noinspection PhpParamsInspection */
822
                \mysqli_ssl_set(
823
                    $this->mysqli_link,
824
                    $this->clientkey,
825
                    $this->clientcert,
826
                    $this->cacert,
827
                    null,
828
                    null
829
                );
830
831
                $flags = \MYSQLI_CLIENT_SSL;
832
            }
833
834
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
835 20
            $this->connected = @\mysqli_real_connect(
836 20
                $this->mysqli_link,
837 20
                $this->hostname,
838 20
                $this->username,
839 20
                $this->password,
840 20
                $this->database,
841 20
                $this->port,
842 20
                $this->socket,
843 20
                (int) $flags
844
            );
845 9
        } catch (\Exception $e) {
846 9
            $error = 'Error connecting to mysql server: ' . $e->getMessage();
847 9
            $this->debug->displayError($error, false);
848
849 9
            throw new DBConnectException($error, 100, $e);
850
        }
851 11
        \mysqli_report(\MYSQLI_REPORT_OFF);
852
853 11
        $errno = \mysqli_connect_errno();
854 11
        if (!$this->connected || $errno) {
855
            $error = 'Error connecting to mysql server: ' . \mysqli_connect_error() . ' (' . $errno . ')';
856
            $this->debug->displayError($error, false);
857
858
            throw new DBConnectException($error, 101);
859
        }
860
861 11
        $this->set_charset($this->charset);
862
863 11
        return $this->isReady();
864
    }
865
866
    /**
867
     * Execute a "delete"-query.
868
     *
869
     * @param string       $table
870
     * @param array|string $where
871
     * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
872
     *
873
     * @throws QueryException
874
     *
875
     * @return false|int <p>false on error</p>
876
     */
877 4 View Code Duplication
    public function delete(string $table, $where, string $databaseName = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
878
    {
879
        // init
880 4
        $table = \trim($table);
881
882 4
        if ($table === '') {
883 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
884
885 3
            return false;
886
        }
887
888 4
        if (\is_string($where)) {
889 3
            $WHERE = $this->escape($where, false);
890 4
        } elseif (\is_array($where)) {
891 4
            $WHERE = $this->_parseArrayPair($where, 'AND');
892
        } else {
893 3
            $WHERE = '';
894
        }
895
896 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...
897
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
898
        }
899
900 4
        $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE (${WHERE})";
901
902
        return $this->query($sql);
903
    }
904
905
    /**
906
     * Ends a transaction and commits if no errors, then ends autocommit.
907
     *
908
     * @return bool <p>This will return true or false indicating success of transactions.</p>
909
     */
910
    public function endTransaction(): bool
911
    {
912 12
        if ($this->in_transaction === false) {
913
            $this->debug->displayError('Error: mysql server is not in transaction!', false);
914
915
            return false;
916
        }
917
918 12
        if (!$this->errors()) {
919 3
            $return = $this->commit();
920
        } else {
921 9
            $this->rollback();
922 9
            $return = false;
923
        }
924
925 12
        if ($this->mysqli_link) {
926 12
            \mysqli_autocommit($this->mysqli_link, true);
927
        } elseif ($this->isDoctrinePDOConnection() === true) {
928
            $this->doctrine_connection->setAutoCommit(true);
929
930
            if ($this->doctrine_connection->isAutoCommit() === true) {
931
                $return = true;
932
            } else {
933
                $return = false;
934
            }
935
        }
936
937 12
        $this->in_transaction = false;
938
939 12
        return $return;
940
    }
941
942
    /**
943
     * Get all errors from "$this->errors".
944
     *
945
     * @return array|false <p>false === on errors</p>
946
     */
947
    public function errors()
948
    {
949 12
        $errors = $this->debug->getErrors();
950
951 12
        return \count($errors) > 0 ? $errors : false;
952
    }
953
954
    /**
955
     * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
956
     *
957
     * @param mixed     $var           bool: convert into "integer"<br />
958
     *                                 int: int (don't change it)<br />
959
     *                                 float: float (don't change it)<br />
960
     *                                 null: null (don't change it)<br />
961
     *                                 array: run escape() for every key => value<br />
962
     *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
963
     * @param bool      $stripe_non_utf8
964
     * @param bool      $html_entity_decode
965
     * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
966
     *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
967
     *                                 <strong>null</strong> => Convert the array into null, every time.
968
     *
969
     * @return mixed
970
     */
971
    public function escape($var = '', bool $stripe_non_utf8 = true, bool $html_entity_decode = false, $convert_array = false)
972
    {
973
        // [empty]
974 110
        if ($var === '') {
975 6
            return '';
976
        }
977
978
        // ''
979 110
        if ($var === "''") {
980
            return "''";
981
        }
982
983
        // check the type
984 110
        $type = \gettype($var);
985
986 110
        if ($type === 'object') {
987 9
            if ($var instanceof \DateTime) {
988 9
                $var = $var->format('Y-m-d H:i:s');
989 9
                $type = 'string';
990 6
            } elseif (\method_exists($var, '__toString')) {
991 6
                $var = (string) $var;
992 6
                $type = 'string';
993
            }
994
        }
995
996
        switch ($type) {
997 110
            case 'boolean':
998 9
                $var = (int) $var;
999
1000 9
                break;
1001
1002 110
            case 'double':
1003 110
            case 'integer':
1004 65
                break;
1005
1006 107
            case 'string':
1007 107
                if ($stripe_non_utf8 === true) {
1008 23
                    $var = UTF8::cleanup($var);
1009
                }
1010
1011 107
                if ($html_entity_decode === true) {
1012 3
                    $var = UTF8::html_entity_decode($var);
1013
                }
1014
1015 107
                $var = \get_magic_quotes_gpc() ? \stripslashes($var) : $var;
1016
1017
                if (
1018 107
                    $this->mysqli_link
1019
                    &&
1020 107
                    $this->mysqli_link instanceof \mysqli
1021
                ) {
1022 107
                    $var = \mysqli_real_escape_string($this->mysqli_link, $var);
1023
                } elseif ($this->isDoctrinePDOConnection() === true) {
1024
                    $var = $this->getDoctrinePDOConnection()->quote($var);
1025
                    $var = \substr($var, 1, -1);
1026
                }
1027
1028 107
                break;
1029
1030 9
            case 'array':
1031 6
                if ($convert_array === null) {
1032 3
                    if ($this->convert_null_to_empty_string === true) {
1033
                        $var = "''";
1034
                    } else {
1035 3
                        $var = 'NULL';
1036
                    }
1037
                } else {
1038 6
                    $varCleaned = [];
1039 6
                    foreach ((array) $var as $key => $value) {
1040 6
                        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
1041 6
                        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
1042
1043
                        /** @noinspection OffsetOperationsInspection */
1044 6
                        $varCleaned[$key] = $value;
1045
                    }
1046
1047 6 View Code Duplication
                    if ($convert_array === true) {
1048 3
                        $varCleaned = \implode(',', $varCleaned);
1049
1050 3
                        $var = $varCleaned;
1051
                    } else {
1052 6
                        $var = $varCleaned;
1053
                    }
1054
                }
1055
1056 6
                break;
1057
1058 9
            case 'NULL':
1059 6
                if ($this->convert_null_to_empty_string === true) {
1060
                    $var = "''";
1061
                } else {
1062 6
                    $var = 'NULL';
1063
                }
1064
1065 6
                break;
1066
1067
            default:
1068 6
                throw new \InvalidArgumentException(\sprintf('Not supported value "%s" of type %s.', \print_r($var, true), $type));
1069
1070
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1071
        }
1072
1073 110
        return $var;
1074
    }
1075
1076
    /**
1077
     * Execute select/insert/update/delete sql-queries.
1078
     *
1079
     * @param string  $query    <p>sql-query</p>
1080
     * @param bool    $useCache optional <p>use cache?</p>
1081
     * @param int     $cacheTTL optional <p>cache-ttl in seconds</p>
1082
     * @param DB|null $db       optional <p>the database connection</p>
1083
     *
1084
     * @throws QueryException
1085
     *
1086
     * @return mixed "array" by "<b>SELECT</b>"-queries<br />
1087
     *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
1088
     *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1089
     *               "true" by e.g. "DROP"-queries<br />
1090
     *               "false" on error
1091
     */
1092
    public static function execSQL(string $query, bool $useCache = false, int $cacheTTL = 3600, self $db = null)
1093
    {
1094
        // init
1095 9
        $cacheKey = null;
1096 9
        if (!$db) {
1097 9
            $db = self::getInstance();
1098
        }
1099
1100 9 View Code Duplication
        if ($useCache === true) {
1101 3
            $cache = new Cache(null, null, false, $useCache);
1102 3
            $cacheKey = 'sql-' . \md5($query);
1103
1104
            if (
1105 3
                $cache->getCacheIsReady() === true
1106
                &&
1107 3
                $cache->existsItem($cacheKey)
1108
            ) {
1109 3
                return $cache->getItem($cacheKey);
1110
            }
1111
        } else {
1112 9
            $cache = false;
1113
        }
1114
1115 9
        $result = $db->query($query);
1116
1117 9
        if ($result instanceof Result) {
1118 3
            $return = $result->fetchAllArray();
1119
1120
            // save into the cache
1121 View Code Duplication
            if (
1122 3
                $cacheKey !== null
1123
                &&
1124 3
                $useCache === true
1125
                &&
1126 3
                $cache instanceof Cache
1127
                &&
1128 3
                $cache->getCacheIsReady() === true
1129
            ) {
1130 3
                $cache->setItem($cacheKey, $return, $cacheTTL);
1131
            }
1132
        } else {
1133 6
            $return = $result;
1134
        }
1135
1136 9
        return $return;
1137
    }
1138
1139
    /**
1140
     * Get all table-names via "SHOW TABLES".
1141
     *
1142
     * @return array
1143
     */
1144
    public function getAllTables(): array
1145
    {
1146 3
        $query = 'SHOW TABLES';
1147 3
        $result = $this->query($query);
1148
1149 3
        return $result->fetchAllArray();
1150
    }
1151
1152
    /**
1153
     * @return array
1154
     */
1155
    public function getConfig()
1156
    {
1157
        $config = [
1158 8
            'hostname'   => $this->hostname,
1159 8
            'username'   => $this->username,
1160 8
            'password'   => $this->password,
1161 8
            'port'       => $this->port,
1162 8
            'database'   => $this->database,
1163 8
            'socket'     => $this->socket,
1164 8
            'charset'    => $this->charset,
1165 8
            'cacert'     => $this->cacert,
1166 8
            'clientcert' => $this->clientcert,
1167 8
            'clientkey'  => $this->clientkey,
1168
        ];
1169
1170 8
        if ($this->doctrine_connection instanceof \Doctrine\DBAL\Connection) {
1171
            $config += $this->doctrine_connection->getParams();
1172
        }
1173
1174 8
        return $config;
1175
    }
1176
1177
    /**
1178
     * @return Debug
1179
     */
1180
    public function getDebugger(): Debug
1181
    {
1182 9
        return $this->debug;
1183
    }
1184
1185
    /**
1186
     * @return \Doctrine\DBAL\Connection|null|null
1187
     */
1188
    public function getDoctrineConnection()
1189
    {
1190 2
        return $this->doctrine_connection;
1191
    }
1192
1193
    /**
1194
     * @return \Doctrine\DBAL\Driver\Connection|false
1195
     */
1196 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...
1197
    {
1198
        if ($this->doctrine_connection) {
1199
            $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1200
            if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
1201
                return $doctrineWrappedConnection;
1202
            }
1203
        }
1204
1205
        return false;
1206
    }
1207
1208
    /**
1209
     * Get errors from "$this->errors".
1210
     *
1211
     * @return array
1212
     */
1213
    public function getErrors(): array
1214
    {
1215 3
        return $this->debug->getErrors();
1216
    }
1217
1218
    /**
1219
     * @param string $hostname             <p>Hostname of the mysql server</p>
1220
     * @param string $username             <p>Username for the mysql connection</p>
1221
     * @param string $password             <p>Password for the mysql connection</p>
1222
     * @param string $database             <p>Database for the mysql connection</p>
1223
     * @param int    $port                 <p>default is (int)3306</p>
1224
     * @param string $charset              <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1225
     * @param bool   $exit_on_error        <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
1226
     *                                     Use false to disable it.</p>
1227
     * @param bool   $echo_on_error        <p>Echo the error if "checkForDev()" returns true.
1228
     *                                     Use false to disable it.</p>
1229
     * @param string $logger_class_name
1230
     * @param string $logger_level         <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1231
     * @param array  $extra_config         <p>
1232
     *                                     're_connect'    => bool<br>
1233
     *                                     'session_to_db' => bool<br>
1234
     *                                     'doctrine'      => \Doctrine\DBAL\Connection<br>
1235
     *                                     'socket'        => 'string (path)'<br>
1236
     *                                     'ssl'           => bool<br>
1237
     *                                     'clientkey'     => 'string (path)'<br>
1238
     *                                     'clientcert'    => 'string (path)'<br>
1239
     *                                     'cacert'        => 'string (path)'<br>
1240
     *                                     </p>
1241
     *
1242
     * @return self
1243
     */
1244
    public static function getInstance(
1245
        string $hostname = '',
1246
        string $username = '',
1247
        string $password = '',
1248
        string $database = '',
1249
        $port = 3306,
1250
        string $charset = 'utf8',
1251
        bool $exit_on_error = true,
1252
        bool $echo_on_error = true,
1253
        string $logger_class_name = '',
1254
        string $logger_level = '',
1255
        array $extra_config = []
1256
    ): self {
1257
        /**
1258
         * @var self[]
1259
         */
1260 206
        static $instance = [];
1261
1262
        /**
1263
         * @var self
1264
         */
1265 206
        static $firstInstance = null;
1266
1267
        // fallback
1268 206
        if (!$charset) {
1269 121
            $charset = 'utf8';
1270
        }
1271
1272
        if (
1273 206
            '' . $hostname . $username . $password . $database . $port . $charset === '' . $port . $charset
1274
            &&
1275 206
            $firstInstance instanceof self
1276
        ) {
1277 123
            if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1278
                $firstInstance->reconnect(true);
1279
            }
1280
1281 123
            return $firstInstance;
1282
        }
1283
1284 125
        $extra_config_string = '';
1285 125
        foreach ($extra_config as $extra_config_key => $extra_config_value) {
1286
            if (\is_object($extra_config_value)) {
1287
                $extra_config_value_tmp = \spl_object_hash($extra_config_value);
1288
            } else {
1289
                $extra_config_value_tmp = (string) $extra_config_value;
1290
            }
1291
            $extra_config_string .= $extra_config_key . $extra_config_value_tmp;
1292
        }
1293
1294 125
        $connection = \md5(
1295 125
            $hostname . $username . $password . $database . $port . $charset . (int) $exit_on_error . (int) $echo_on_error . $logger_class_name . $logger_level . $extra_config_string
1296
        );
1297
1298 125
        if (!isset($instance[$connection])) {
1299 23
            $instance[$connection] = new self(
1300 23
                $hostname,
1301 23
                $username,
1302 23
                $password,
1303 23
                $database,
1304 23
                $port,
1305 23
                $charset,
1306 23
                $exit_on_error,
1307 23
                $echo_on_error,
1308 23
                $logger_class_name,
1309 23
                $logger_level,
1310 23
                $extra_config
1311
            );
1312
1313 5
            if ($firstInstance === null) {
1314 1
                $firstInstance = $instance[$connection];
1315
            }
1316
        }
1317
1318 113
        if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1319
            $instance[$connection]->reconnect(true);
1320
        }
1321
1322 113
        return $instance[$connection];
1323
    }
1324
1325
    /**
1326
     * @param \Doctrine\DBAL\Connection $doctrine
1327
     * @param string                    $charset       <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1328
     * @param bool                      $exit_on_error <p>Throw a 'Exception' when a query failed, otherwise it will
1329
     *                                                 return 'false'. Use false to disable it.</p>
1330
     * @param bool                      $echo_on_error <p>Echo the error if "checkForDev()" returns true.
1331
     *                                                 Use false to disable it.</p>
1332
     * @param string                    $logger_class_name
1333
     * @param string                    $logger_level  <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1334
     * @param array                     $extra_config  <p>
1335
     *                                                 're_connect'    => bool<br>
1336
     *                                                 'session_to_db' => bool<br>
1337
     *                                                 'doctrine'      => \Doctrine\DBAL\Connection<br>
1338
     *                                                 'socket'        => 'string (path)'<br>
1339
     *                                                 'ssl'           => bool<br>
1340
     *                                                 'clientkey'     => 'string (path)'<br>
1341
     *                                                 'clientcert'    => 'string (path)'<br>
1342
     *                                                 'cacert'        => 'string (path)'<br>
1343
     *                                                 </p>
1344
     *
1345
     * @return self
1346
     */
1347
    public static function getInstanceDoctrineHelper(
1348
        \Doctrine\DBAL\Connection $doctrine,
1349
        string $charset = 'utf8',
1350
        bool $exit_on_error = true,
1351
        bool $echo_on_error = true,
1352
        string $logger_class_name = '',
1353
        string $logger_level = '',
1354
        array $extra_config = []
1355
    ): self {
1356 55
        $extra_config['doctrine'] = $doctrine;
1357
1358 55
        return self::getInstance(
1359 55
            '',
1360 55
            '',
1361 55
            '',
1362 55
            '',
1363 55
            3306,
1364 55
            $charset,
1365 55
            $exit_on_error,
1366 55
            $echo_on_error,
1367 55
            $logger_class_name,
1368 55
            $logger_level,
1369 55
            $extra_config
1370
        );
1371
    }
1372
1373
    /**
1374
     * Get the mysqli-link (link identifier returned by mysqli-connect).
1375
     *
1376
     * @return \mysqli|null
1377
     */
1378
    public function getLink()
1379
    {
1380 52
        return $this->mysqli_link;
1381
    }
1382
1383
    /**
1384
     * Get the current charset.
1385
     *
1386
     * @return string
1387
     */
1388
    public function get_charset(): string
1389
    {
1390 3
        return $this->charset;
1391
    }
1392
1393
    /**
1394
     * Check if we are in a transaction.
1395
     *
1396
     * @return bool
1397
     */
1398
    public function inTransaction(): bool
1399
    {
1400
        return $this->in_transaction;
1401
    }
1402
1403
    /**
1404
     * Execute a "insert"-query.
1405
     *
1406
     * @param string      $table
1407
     * @param array       $data
1408
     * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1409
     *
1410
     * @throws QueryException
1411
     *
1412
     * @return false|int <p>false on error</p>
1413
     */
1414
    public function insert(string $table, array $data = [], string $databaseName = null)
1415
    {
1416
        // init
1417 74
        $table = \trim($table);
1418
1419 74
        if ($table === '') {
1420 6
            $this->debug->displayError('Invalid table name, table name in empty.', false);
1421
1422 6
            return false;
1423
        }
1424
1425 71
        if (\count($data) === 0) {
1426 9
            $this->debug->displayError('Invalid data for INSERT, data is empty.', false);
1427
1428 9
            return false;
1429
        }
1430
1431 65
        $SET = $this->_parseArrayPair($data);
1432
1433 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...
1434
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
1435
        }
1436
1437 65
        $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET ${SET}";
1438
1439 65
        return $this->query($sql);
1440
    }
1441
1442
    /**
1443
     * Returns the auto generated id used in the last query.
1444
     *
1445
     * @return int|string
1446
     */
1447
    public function insert_id()
1448
    {
1449 100
        if ($this->mysqli_link) {
1450 100
            return \mysqli_insert_id($this->mysqli_link);
1451
        }
1452
1453
        if ($this->getDoctrinePDOConnection()) {
1454
            return $this->getDoctrinePDOConnection()->lastInsertId();
1455
        }
1456
    }
1457
1458
    /**
1459
     * @return bool
1460
     */
1461
    public function isDoctrineMySQLiConnection(): bool
1462
    {
1463
        if ($this->doctrine_connection) {
1464
            $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1465
            if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliConnection) {
1466
                return true;
1467
            }
1468
        }
1469
1470
        return false;
1471
    }
1472
1473
    /**
1474
     * @return bool
1475
     */
1476 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...
1477
    {
1478 9
        if ($this->doctrine_connection) {
1479
            $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1480
            if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
1481
                return true;
1482
            }
1483
        }
1484
1485 9
        return false;
1486
    }
1487
1488
    /**
1489
     * Check if db-connection is ready.
1490
     *
1491
     * @return bool
1492
     */
1493
    public function isReady(): bool
1494
    {
1495 160
        return $this->connected ? true : false;
1496
    }
1497
1498
    /**
1499
     * Get the last sql-error.
1500
     *
1501
     * @return false|string <p>false === there was no error</p>
1502
     */
1503
    public function lastError()
1504
    {
1505 3
        $errors = $this->debug->getErrors();
1506
1507 3
        return \count($errors) > 0 ? \end($errors) : false;
1508
    }
1509
1510
    /**
1511
     * Execute a sql-multi-query.
1512
     *
1513
     * @param string $sql
1514
     *
1515
     * @throws QueryException
1516
     *
1517
     * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1518
     *                        "bool" by only "<b>INSERT</b>"-queries<br />
1519
     *                        "bool" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1520
     *                        "bool" by only by e.g. "DROP"-queries<br />
1521
     */
1522
    public function multi_query(string $sql)
1523
    {
1524 3
        if (!$this->isReady()) {
1525
            return false;
1526
        }
1527
1528 3 View Code Duplication
        if (!$sql || $sql === '') {
1529 3
            $this->debug->displayError('Can not execute an empty query.', false);
1530
1531 3
            return false;
1532
        }
1533
1534 3
        if ($this->isDoctrinePDOConnection() === true) {
1535
            $query_start_time = \microtime(true);
1536
            $queryException = null;
1537
            $query_result_doctrine = false;
1538
1539
            try {
1540
                $query_result_doctrine = $this->doctrine_connection->prepare($sql);
1541
                $resultTmp = $query_result_doctrine->execute();
1542
                $mysqli_field_count = $query_result_doctrine->columnCount();
1543
            } catch (\Exception $e) {
1544
                $resultTmp = false;
1545
                $mysqli_field_count = null;
1546
1547
                $queryException = $e;
1548
            }
1549
1550
            $query_duration = \microtime(true) - $query_start_time;
1551
1552
            $this->debug->logQuery($sql, $query_duration, 0);
1553
1554
            $returnTheResult = false;
1555
            $result = [];
1556
1557
            if ($resultTmp) {
1558
                if ($mysqli_field_count) {
1559
                    if (
1560
                        $query_result_doctrine
1561
                        &&
1562
                        $query_result_doctrine instanceof \Doctrine\DBAL\Statement
1563
                    ) {
1564
                        $result = $query_result_doctrine;
1565
                    }
1566
                } else {
1567
                    $result = $resultTmp;
1568
                }
1569
1570
                if (
1571
                    $result instanceof \Doctrine\DBAL\Statement
1572
                    &&
1573
                    $result->columnCount() > 0
1574
                ) {
1575
                    $returnTheResult = true;
1576
1577
                    // return query result object
1578
                    $result = [new Result($sql, $result)];
1579
                } else {
1580
                    $result = [$result];
1581
                }
1582
            } else {
1583
1584
                // log the error query
1585
                $this->debug->logQuery($sql, $query_duration, 0, true);
1586
1587
                if (
1588
                    isset($queryException)
1589
                    &&
1590
                    $queryException instanceof \Doctrine\DBAL\Query\QueryException
1591
                ) {
1592
                    return $this->queryErrorHandling($queryException->getMessage(), $queryException->getCode(), $sql, false, true);
1593
                }
1594
            }
1595
        } else {
1596 3
            $query_start_time = \microtime(true);
1597 3
            $resultTmp = \mysqli_multi_query($this->mysqli_link, $sql);
1598 3
            $query_duration = \microtime(true) - $query_start_time;
1599
1600 3
            $this->debug->logQuery($sql, $query_duration, 0);
1601
1602 3
            $returnTheResult = false;
1603 3
            $result = [];
1604
1605 3
            if ($resultTmp) {
1606
                do {
1607 3
                    $resultTmpInner = \mysqli_store_result($this->mysqli_link);
1608
1609 3
                    if ($resultTmpInner instanceof \mysqli_result) {
1610 3
                        $returnTheResult = true;
1611 3
                        $result[] = new Result($sql, $resultTmpInner);
1612
                    } elseif (
1613 3
                        $resultTmpInner === true
1614
                        ||
1615 3
                        !\mysqli_errno($this->mysqli_link)
1616
                    ) {
1617 3
                        $result[] = true;
1618
                    } else {
1619
                        $result[] = false;
1620
                    }
1621 3
                } while (\mysqli_more_results($this->mysqli_link) === true ? \mysqli_next_result($this->mysqli_link) : false);
1622
            } else {
1623
1624
                // log the error query
1625 3
                $this->debug->logQuery($sql, $query_duration, 0, true);
1626
1627 3
                return $this->queryErrorHandling(\mysqli_error($this->mysqli_link), \mysqli_errno($this->mysqli_link), $sql, false, true);
1628
            }
1629
        }
1630
1631
        // return the result only if there was a "SELECT"-query
1632 3
        if ($returnTheResult === true) {
1633 3
            return $result;
1634
        }
1635
1636
        if (
1637 3
            \count($result) > 0
1638
            &&
1639 3
            \in_array(false, $result, true) === false
1640
        ) {
1641 3
            return true;
1642
        }
1643
1644
        return false;
1645
    }
1646
1647
    /**
1648
     * Count number of rows found matching a specific query.
1649
     *
1650
     * @param string $query
1651
     *
1652
     * @return int
1653
     */
1654
    public function num_rows(string $query): int
1655
    {
1656 3
        $check = $this->query($query);
1657
1658
        if (
1659 3
            $check === false
1660
            ||
1661 3
            !$check instanceof Result
1662
        ) {
1663
            return 0;
1664
        }
1665
1666 3
        return $check->num_rows;
1667
    }
1668
1669
    /**
1670
     * Pings a server connection, or tries to reconnect
1671
     * if the connection has gone down.
1672
     *
1673
     * @return bool
1674
     */
1675
    public function ping(): bool
1676
    {
1677 9
        if ($this->connected === false) {
1678 3
            return false;
1679
        }
1680
1681 6
        if ($this->isDoctrinePDOConnection() === true) {
1682
            return $this->doctrine_connection->ping();
1683
        }
1684
1685
        if (
1686 6
            $this->mysqli_link
1687
            &&
1688 6
            $this->mysqli_link instanceof \mysqli
1689
        ) {
1690 6
            return \mysqli_ping($this->mysqli_link);
1691
        }
1692
1693
        return false;
1694
    }
1695
1696
    /**
1697
     * Get a new "Prepare"-Object for your sql-query.
1698
     *
1699
     * @param string $query
1700
     *
1701
     * @return Prepare
1702
     */
1703
    public function prepare(string $query): Prepare
1704
    {
1705 2
        return new Prepare($this, $query);
1706
    }
1707
1708
    /**
1709
     * Execute a sql-query and return the result-array for select-statements.
1710
     *
1711
     * @param string $query
1712
     *
1713
     * @throws \Exception
1714
     *
1715
     * @return mixed
1716
     *
1717
     * @deprecated
1718
     */
1719
    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...
1720
    {
1721 3
        $db = self::getInstance();
1722
1723 3
        $args = \func_get_args();
1724
        /** @noinspection SuspiciousAssignmentsInspection */
1725 3
        $query = \array_shift($args);
1726 3
        $query = \str_replace('?', '%s', $query);
1727 3
        $args = \array_map(
1728
            [
1729 3
                $db,
1730 3
                'escape',
1731
            ],
1732 3
            $args
1733
        );
1734 3
        \array_unshift($args, $query);
1735 3
        $query = \sprintf(...$args);
1736 3
        $result = $db->query($query);
1737
1738 3
        if ($result instanceof Result) {
1739 3
            return $result->fetchAllArray();
1740
        }
1741
1742 3
        return $result;
1743
    }
1744
1745
    /**
1746
     * Execute a sql-query.
1747
     *
1748
     * example:
1749
     * <code>
1750
     * $sql = "INSERT INTO TABLE_NAME_HERE
1751
     *   SET
1752
     *     foo = :foo,
1753
     *     bar = :bar
1754
     * ";
1755
     * $insert_id = $db->query(
1756
     *   $sql,
1757
     *   [
1758
     *     'foo' => 1.1,
1759
     *     'bar' => 1,
1760
     *   ]
1761
     * );
1762
     * </code>
1763
     *
1764
     * @param string     $sql               <p>The sql query-string.</p>
1765
     * @param array|bool $params            <p>
1766
     *                                      "array" of sql-query-parameters<br/>
1767
     *                                      "false" if you don't need any parameter (default)<br/>
1768
     *                                      </p>
1769
     *
1770
     * @throws QueryException
1771
     *
1772
     * @return bool|int|Result              <p>
1773
     *                                      "Result" by "<b>SELECT</b>"-queries<br />
1774
     *                                      "int|string" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
1775
     *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1776
     *                                      "true" by e.g. "DROP"-queries<br />
1777
     *                                      "false" on error
1778
     *                                      </p>
1779
     */
1780
    public function query(string $sql = '', $params = false)
1781
    {
1782 140
        if (!$this->isReady()) {
1783
            return false;
1784
        }
1785
1786 140 View Code Duplication
        if (!$sql || $sql === '') {
1787 12
            $this->debug->displayError('Can not execute an empty query.', false);
1788
1789 12
            return false;
1790
        }
1791
1792
        if (
1793 134
            $params !== false
1794
            &&
1795 134
            \is_array($params)
1796
            &&
1797 134
            \count($params) > 0
1798
        ) {
1799 7
            $parseQueryParams = $this->_parseQueryParams($sql, $params);
1800 7
            $parseQueryParamsByName = $this->_parseQueryParamsByName($parseQueryParams['sql'], $parseQueryParams['params']);
1801 7
            $sql = $parseQueryParamsByName['sql'];
1802
        }
1803
1804
        // DEBUG
1805
        // var_dump($params);
1806
        // echo $sql . "\n";
1807
1808 134
        $query_start_time = \microtime(true);
1809 134
        $queryException = null;
1810 134
        $query_result_doctrine = false;
1811
1812 134
        if ($this->doctrine_connection) {
1813
            try {
1814
                $query_result_doctrine = $this->doctrine_connection->prepare($sql);
1815
                $query_result = $query_result_doctrine->execute();
1816
                $mysqli_field_count = $query_result_doctrine->columnCount();
1817
            } catch (\Exception $e) {
1818
                $query_result = false;
1819
                $mysqli_field_count = null;
1820
1821
                $queryException = $e;
1822
            }
1823
        } else {
1824 134
            $query_result = \mysqli_real_query($this->mysqli_link, $sql);
1825 134
            $mysqli_field_count = \mysqli_field_count($this->mysqli_link);
1826
        }
1827
1828 134
        $query_duration = \microtime(true) - $query_start_time;
1829
1830 134
        $this->query_count++;
1831
1832 134
        if ($mysqli_field_count) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mysqli_field_count of type null|integer 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...
1833 99
            if ($this->doctrine_connection) {
1834
                $result = false;
1835
                if (
1836
                    $query_result_doctrine
1837
                    &&
1838
                    $query_result_doctrine instanceof \Doctrine\DBAL\Statement
1839
                ) {
1840
                    $result = $query_result_doctrine;
1841
                }
1842
            } else {
1843 99
                $result = \mysqli_store_result($this->mysqli_link);
1844
            }
1845
        } else {
1846 106
            $result = $query_result;
1847
        }
1848
1849
        if (
1850 134
            $result instanceof \Doctrine\DBAL\Statement
1851
            &&
1852 134
            $result->columnCount() > 0
1853
        ) {
1854
1855
            // log the select query
1856
            $this->debug->logQuery($sql, $query_duration, $mysqli_field_count);
1857
1858
            // return query result object
1859
            return new Result($sql, $result);
1860
        }
1861
1862 134
        if ($result instanceof \mysqli_result) {
1863
1864
            // log the select query
1865 96
            $this->debug->logQuery($sql, $query_duration, $mysqli_field_count);
1866
1867
            // return query result object
1868 96
            return new Result($sql, $result);
1869
        }
1870
1871 112
        if ($query_result === true) {
1872
1873
            // "INSERT" || "REPLACE"
1874 103
            if (\preg_match('/^\s*?(?:INSERT|REPLACE)\s+/i', $sql)) {
1875 100
                $insert_id = $this->insert_id();
1876
1877 100
                $this->debug->logQuery($sql, $query_duration, $insert_id);
1878
1879 100
                return $insert_id;
1880
            }
1881
1882
            // "UPDATE" || "DELETE"
1883 50
            if (\preg_match('/^\s*?(?:UPDATE|DELETE)\s+/i', $sql)) {
1884 25
                if ($this->mysqli_link) {
1885 25
                    $this->affected_rows = $this->affected_rows();
1886
                } elseif ($query_result_doctrine) {
1887
                    $this->affected_rows = $query_result_doctrine->rowCount();
1888
                }
1889
1890 25
                $this->debug->logQuery($sql, $query_duration, $this->affected_rows);
1891
1892 25
                return $this->affected_rows;
1893
            }
1894
1895
            // log the ? query
1896 25
            $this->debug->logQuery($sql, $query_duration, 0);
1897
1898 25
            return true;
1899
        }
1900
1901
        // log the error query
1902 33
        $this->debug->logQuery($sql, $query_duration, 0, true);
1903
1904 33
        if ($queryException) {
1905
            return $this->queryErrorHandling($queryException->getMessage(), $queryException->getCode(), $sql, $params);
1906
        }
1907
1908 33
        if ($this->mysqli_link) {
1909 33
            return $this->queryErrorHandling(\mysqli_error($this->mysqli_link), \mysqli_errno($this->mysqli_link), $sql, $params);
1910
        }
1911
1912
        return false;
1913
    }
1914
1915
    /**
1916
     * Error-handling for the sql-query.
1917
     *
1918
     * @param string     $errorMessage
1919
     * @param int        $errorNumber
1920
     * @param string     $sql
1921
     * @param array|bool $sqlParams <p>false if there wasn't any parameter</p>
1922
     * @param bool       $sqlMultiQuery
1923
     *
1924
     * @throws QueryException
1925
     * @throws DBGoneAwayException
1926
     *
1927
     * @return false|mixed
1928
     */
1929
    private function queryErrorHandling(string $errorMessage, int $errorNumber, string $sql, $sqlParams = false, bool $sqlMultiQuery = false)
1930
    {
1931
        if (
1932 39
            $errorMessage === 'DB server has gone away'
1933
            ||
1934 36
            $errorMessage === 'MySQL server has gone away'
1935
            ||
1936 39
            $errorNumber === 2006
1937
        ) {
1938 3
            static $RECONNECT_COUNTER;
1939
1940
            // exit if we have more then 3 "DB server has gone away"-errors
1941 3
            if ($RECONNECT_COUNTER > 3) {
1942
                $this->debug->mailToAdmin('DB-Fatal-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql, 5);
1943
1944
                throw new DBGoneAwayException($errorMessage);
1945
            }
1946
1947 3
            $this->debug->mailToAdmin('DB-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1948
1949
            // reconnect
1950 3
            $RECONNECT_COUNTER++;
1951 3
            $this->reconnect(true);
1952
1953
            // re-run the current (non multi) query
1954 3
            if ($sqlMultiQuery === false) {
1955 3
                return $this->query($sql, $sqlParams);
1956
            }
1957
1958
            return false;
1959
        }
1960
1961 36
        $this->debug->mailToAdmin('SQL-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1962
1963 36
        $force_exception_after_error = null; // auto
1964 36
        if ($this->in_transaction === true) {
1965 12
            $force_exception_after_error = false;
1966
        }
1967
        // this query returned an error, we must display it (only for dev) !!!
1968
1969 36
        $this->debug->displayError($errorMessage . '(' . $errorNumber . ') ' . ' | ' . $sql, $force_exception_after_error);
1970
1971 36
        return false;
1972
    }
1973
1974
    /**
1975
     * Quote && Escape e.g. a table name string.
1976
     *
1977
     * @param mixed $str
1978
     *
1979
     * @return string
1980
     */
1981
    public function quote_string($str): string
1982
    {
1983 86
        $str = \str_replace(
1984 86
            '`',
1985 86
            '``',
1986 86
            \trim(
1987 86
                (string) $this->escape($str, false),
1988 86
                '`'
1989
            )
1990
        );
1991
1992 86
        return '`' . $str . '`';
1993
    }
1994
1995
    /**
1996
     * Reconnect to the MySQL-Server.
1997
     *
1998
     * @param bool $checkViaPing
1999
     *
2000
     * @return bool
2001
     */
2002
    public function reconnect(bool $checkViaPing = false): bool
2003
    {
2004 7
        $ping = false;
2005 7
        if ($checkViaPing === true) {
2006 6
            $ping = $this->ping();
2007
        }
2008
2009 7
        if ($ping === false) {
2010 7
            $this->connected = false;
2011 7
            $this->connect();
2012
        }
2013
2014 7
        return $this->isReady();
2015
    }
2016
2017
    /**
2018
     * Execute a "replace"-query.
2019
     *
2020
     * @param string      $table
2021
     * @param array       $data
2022
     * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2023
     *
2024
     * @throws QueryException
2025
     *
2026
     * @return false|int <p>false on error</p>
2027
     */
2028
    public function replace(string $table, array $data = [], string $databaseName = null)
2029
    {
2030
        // init
2031 3
        $table = \trim($table);
2032
2033 3
        if ($table === '') {
2034 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
2035
2036 3
            return false;
2037
        }
2038
2039 3
        if (\count($data) === 0) {
2040 3
            $this->debug->displayError('Invalid data for REPLACE, data is empty.', false);
2041
2042 3
            return false;
2043
        }
2044
2045
        // extracting column names
2046 3
        $columns = \array_keys($data);
2047 3
        foreach ($columns as $k => $_key) {
2048
            /** @noinspection AlterInForeachInspection */
2049 3
            $columns[$k] = $this->quote_string($_key);
2050
        }
2051
2052 3
        $columns = \implode(',', $columns);
2053
2054
        // extracting values
2055 3
        foreach ($data as $k => $_value) {
2056
            /** @noinspection AlterInForeachInspection */
2057 3
            $data[$k] = $this->secure($_value);
2058
        }
2059 3
        $values = \implode(',', $data);
2060
2061 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...
2062
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2063
        }
2064
2065 3
        $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " (${columns}) VALUES (${values})";
2066
2067 3
        return $this->query($sql);
2068
    }
2069
2070
    /**
2071
     * Rollback in a transaction and end the transaction.
2072
     *
2073
     * @return bool <p>bool true on success, false otherwise.</p>
2074
     */
2075 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...
2076
    {
2077 12
        if ($this->in_transaction === false) {
2078
            $this->debug->displayError('Error: mysql server is not in transaction!', false);
2079
2080
            return false;
2081
        }
2082
2083 12
        if ($this->mysqli_link) {
2084 12
            $return = \mysqli_rollback($this->mysqli_link);
2085 12
            \mysqli_autocommit($this->mysqli_link, true);
2086
        } elseif ($this->isDoctrinePDOConnection() === true) {
2087
            $this->doctrine_connection->rollBack();
2088
            $this->doctrine_connection->setAutoCommit(true);
2089
2090
            if ($this->doctrine_connection->isAutoCommit() === true) {
2091
                $return = true;
2092
            } else {
2093
                $return = false;
2094
            }
2095
        }
2096
2097 12
        $this->in_transaction = false;
2098
2099 12
        return $return;
0 ignored issues
show
Bug introduced by
The variable $return does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2100
    }
2101
2102
    /**
2103
     * Try to secure a variable, so can you use it in sql-queries.
2104
     *
2105
     * <p>
2106
     * <strong>int:</strong> (also strings that contains only an int-value)<br />
2107
     * 1. parse into "int"
2108
     * </p><br />
2109
     *
2110
     * <p>
2111
     * <strong>float:</strong><br />
2112
     * 1. return "float"
2113
     * </p><br />
2114
     *
2115
     * <p>
2116
     * <strong>string:</strong><br />
2117
     * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
2118
     * 2. trim '<br />
2119
     * 3. escape the string (and remove non utf-8 chars)<br />
2120
     * 4. trim ' again (because we maybe removed some chars)<br />
2121
     * 5. add ' around the new string<br />
2122
     * </p><br />
2123
     *
2124
     * <p>
2125
     * <strong>array:</strong><br />
2126
     * 1. return null
2127
     * </p><br />
2128
     *
2129
     * <p>
2130
     * <strong>object:</strong><br />
2131
     * 1. return false
2132
     * </p><br />
2133
     *
2134
     * <p>
2135
     * <strong>null:</strong><br />
2136
     * 1. return null
2137
     * </p>
2138
     *
2139
     * @param mixed     $var
2140
     * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
2141
     *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
2142
     *                                 <strong>null</strong> => Convert the array into null, every time.
2143
     *
2144
     * @return mixed
2145
     */
2146
    public function secure($var, $convert_array = true)
2147
    {
2148 87
        if (\is_array($var)) {
2149 6
            if ($convert_array === null) {
2150
                if ($this->convert_null_to_empty_string === true) {
2151
                    $var = "''";
2152
                } else {
2153
                    $var = 'NULL';
2154
                }
2155
            } else {
2156 6
                $varCleaned = [];
2157 6
                foreach ((array) $var as $key => $value) {
2158 6
                    $key = $this->escape($key, false, false, $convert_array);
2159 6
                    $value = $this->secure($value);
2160
2161
                    /** @noinspection OffsetOperationsInspection */
2162 6
                    $varCleaned[$key] = $value;
2163
                }
2164
2165 6 View Code Duplication
                if ($convert_array === true) {
2166 6
                    $varCleaned = \implode(',', $varCleaned);
2167
2168 6
                    $var = $varCleaned;
2169
                } else {
2170
                    $var = $varCleaned;
2171
                }
2172
            }
2173
2174 6
            return $var;
2175
        }
2176
2177 87
        if ($var === '') {
2178 6
            return "''";
2179
        }
2180
2181 87
        if ($var === "''") {
2182 3
            return "''";
2183
        }
2184
2185 87
        if ($var === null) {
2186 3
            if ($this->convert_null_to_empty_string === true) {
2187 3
                return "''";
2188
            }
2189
2190 3
            return 'NULL';
2191
        }
2192
2193 87
        if (\in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
2194 3
            return $var;
2195
        }
2196
2197 87
        if (\is_string($var)) {
2198 77
            $var = \trim($var, "'");
2199
        }
2200
2201 87
        $var = $this->escape($var, false, false, null);
2202
2203 84
        if (\is_string($var)) {
2204 77
            $var = "'" . \trim($var, "'") . "'";
2205
        }
2206
2207 84
        return $var;
2208
    }
2209
2210
    /**
2211
     * Execute a "select"-query.
2212
     *
2213
     * @param string       $table
2214
     * @param array|string $where
2215
     * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2216
     *
2217
     * @throws QueryException
2218
     *
2219
     * @return false|Result <p>false on error</p>
2220
     */
2221 View Code Duplication
    public function select(string $table, $where = '1=1', string $databaseName = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2222
    {
2223
        // init
2224 62
        $table = \trim($table);
2225
2226 62
        if ($table === '') {
2227 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
2228
2229 3
            return false;
2230
        }
2231
2232 62
        if (\is_string($where)) {
2233 25
            $WHERE = $this->escape($where, false);
2234 41
        } elseif (\is_array($where)) {
2235 41
            $WHERE = $this->_parseArrayPair($where, 'AND');
2236
        } else {
2237 3
            $WHERE = '';
2238
        }
2239
2240 62
        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...
2241
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2242
        }
2243
2244 62
        $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE (${WHERE})";
2245
2246 62
        return $this->query($sql);
2247
    }
2248
2249
    /**
2250
     * Selects a different database than the one specified on construction.
2251
     *
2252
     * @param string $database <p>Database name to switch to.</p>
2253
     *
2254
     * @return bool <p>bool true on success, false otherwise.</p>
2255
     */
2256
    public function select_db(string $database): bool
2257
    {
2258
        if (!$this->isReady()) {
2259
            return false;
2260
        }
2261
2262
        if ($this->mysqli_link) {
2263
            return \mysqli_select_db($this->mysqli_link, $database);
2264
        }
2265
2266
        if ($this->isDoctrinePDOConnection()) {
2267
            return $this->query('use :database', ['database' => $database]);
2268
        }
2269
2270
        return false;
2271
    }
2272
2273
    /**
2274
     * @param array $extra_config   <p>
2275
     *                              'session_to_db' => false|true<br>
2276
     *                              'socket' => 'string (path)'<br>
2277
     *                              'ssl' => 'bool'<br>
2278
     *                              'clientkey' => 'string (path)'<br>
2279
     *                              'clientcert' => 'string (path)'<br>
2280
     *                              'cacert' => 'string (path)'<br>
2281
     *                              </p>
2282
     */
2283
    public function setConfigExtra(array $extra_config)
2284
    {
2285 23
        if (isset($extra_config['session_to_db'])) {
2286
            $this->session_to_db = (bool) $extra_config['session_to_db'];
2287
        }
2288
2289 23
        if (isset($extra_config['doctrine'])) {
2290
            if ($extra_config['doctrine'] instanceof \Doctrine\DBAL\Connection) {
2291
                $this->doctrine_connection = $extra_config['doctrine'];
2292
            } else {
2293
                throw new DBConnectException('Error "doctrine"-connection is not valid');
2294
            }
2295
        }
2296
2297 23
        if (isset($extra_config['socket'])) {
2298
            $this->socket = $extra_config['socket'];
2299
        }
2300
2301 23
        if (isset($extra_config['ssl'])) {
2302
            $this->ssl = $extra_config['ssl'];
2303
        }
2304
2305 23
        if (isset($extra_config['clientkey'])) {
2306
            $this->clientkey = $extra_config['clientkey'];
2307
        }
2308
2309 23
        if (isset($extra_config['clientcert'])) {
2310
            $this->clientcert = $extra_config['clientcert'];
2311
        }
2312
2313 23
        if (isset($extra_config['cacert'])) {
2314
            $this->cacert = $extra_config['cacert'];
2315
        }
2316 23
    }
2317
2318
    /**
2319
     * Set the current charset.
2320
     *
2321
     * @param string $charset
2322
     *
2323
     * @return bool
2324
     */
2325
    public function set_charset(string $charset): bool
2326
    {
2327 14
        $charsetLower = \strtolower($charset);
2328 14
        if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
2329 8
            $charset = 'utf8';
2330
        }
2331 14
        if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
2332 8
            $charset = 'utf8mb4';
2333
        }
2334
2335 14
        $this->charset = $charset;
2336
2337
        if (
2338 14
            $this->mysqli_link
2339
            &&
2340 14
            $this->mysqli_link instanceof \mysqli
2341
        ) {
2342 14
            $return = \mysqli_set_charset($this->mysqli_link, $charset);
2343
2344
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
2345 14
            @\mysqli_query($this->mysqli_link, 'SET CHARACTER SET ' . $charset);
2346
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
2347 14
            @\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...
2348
        } elseif ($this->isDoctrinePDOConnection() === true) {
2349
            $doctrineWrappedConnection = $this->getDoctrinePDOConnection();
2350
2351
            $doctrineWrappedConnection->exec('SET CHARACTER SET ' . $charset);
2352
            $doctrineWrappedConnection->exec("SET NAMES '" . $charset . "'");
2353
2354
            $return = true;
2355
        } else {
2356
            throw new DBConnectException('Can not set the charset');
2357
        }
2358
2359 14
        return $return;
2360
    }
2361
2362
    /**
2363
     * Set the option to convert null to "''" (empty string).
2364
     *
2365
     * Used in secure() => select(), insert(), update(), delete()
2366
     *
2367
     * @deprecated It's not recommended to convert NULL into an empty string!
2368
     *
2369
     * @param bool $bool
2370
     *
2371
     * @return self
2372
     */
2373
    public function set_convert_null_to_empty_string(bool $bool): self
2374
    {
2375 3
        $this->convert_null_to_empty_string = $bool;
2376
2377 3
        return $this;
2378
    }
2379
2380
    /**
2381
     * Enables or disables internal report functions
2382
     *
2383
     * @see http://php.net/manual/en/function.mysqli-report.php
2384
     *
2385
     * @param int $flags <p>
2386
     *                   <table>
2387
     *                   Supported flags
2388
     *                   <tr valign="top">
2389
     *                   <td>Name</td>
2390
     *                   <td>Description</td>
2391
     *                   </tr>
2392
     *                   <tr valign="top">
2393
     *                   <td><b>MYSQLI_REPORT_OFF</b></td>
2394
     *                   <td>Turns reporting off</td>
2395
     *                   </tr>
2396
     *                   <tr valign="top">
2397
     *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
2398
     *                   <td>Report errors from mysqli function calls</td>
2399
     *                   </tr>
2400
     *                   <tr valign="top">
2401
     *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
2402
     *                   <td>
2403
     *                   Throw <b>mysqli_sql_exception</b> for errors
2404
     *                   instead of warnings
2405
     *                   </td>
2406
     *                   </tr>
2407
     *                   <tr valign="top">
2408
     *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
2409
     *                   <td>Report if no index or bad index was used in a query</td>
2410
     *                   </tr>
2411
     *                   <tr valign="top">
2412
     *                   <td><b>MYSQLI_REPORT_ALL</b></td>
2413
     *                   <td>Set all options (report all)</td>
2414
     *                   </tr>
2415
     *                   </table>
2416
     *                   </p>
2417
     *
2418
     * @return bool
2419
     */
2420
    public function set_mysqli_report(int $flags): bool
2421
    {
2422
        if (
2423
            $this->mysqli_link
2424
            &&
2425
            $this->mysqli_link instanceof \mysqli
2426
        ) {
2427
            return \mysqli_report($flags);
2428
        }
2429
2430
        return false;
2431
    }
2432
2433
    /**
2434
     * Show config errors by throw exceptions.
2435
     *
2436
     * @throws \InvalidArgumentException
2437
     *
2438
     * @return bool
2439
     */
2440
    public function showConfigError(): bool
2441
    {
2442
        // check if a doctrine connection is already open, first
2443
        if (
2444 23
            $this->doctrine_connection
2445
            &&
2446 23
            $this->doctrine_connection->isConnected()
2447
        ) {
2448
            return true;
2449
        }
2450
2451
        if (
2452 23
            !$this->hostname
2453
            ||
2454 20
            !$this->username
2455
            ||
2456 23
            !$this->database
2457
        ) {
2458 9
            if (!$this->hostname) {
2459 3
                throw new \InvalidArgumentException('no-sql-hostname');
2460
            }
2461
2462 6
            if (!$this->username) {
2463 3
                throw new \InvalidArgumentException('no-sql-username');
2464
            }
2465
2466 3
            if (!$this->database) {
2467 3
                throw new \InvalidArgumentException('no-sql-database');
2468
            }
2469
2470
            return false;
2471
        }
2472
2473 14
        return true;
2474
    }
2475
2476
    /**
2477
     * alias: "beginTransaction()"
2478
     */
2479
    public function startTransaction(): bool
2480
    {
2481 3
        return $this->beginTransaction();
2482
    }
2483
2484
    /**
2485
     * Determine if database table exists
2486
     *
2487
     * @param string $table
2488
     *
2489
     * @return bool
2490
     */
2491
    public function table_exists(string $table): bool
2492
    {
2493 3
        $check = $this->query('SELECT 1 FROM ' . $this->quote_string($table));
2494
2495 3
        return $check !== false
2496
               &&
2497 3
               $check instanceof Result
2498
               &&
2499 3
               $check->num_rows > 0;
2500
    }
2501
2502
    /**
2503
     * Execute a callback inside a transaction.
2504
     *
2505
     * @param callback $callback <p>The callback to run inside the transaction, if it's throws an "Exception" or if it's
2506
     *                           returns "false", all SQL-statements in the callback will be rollbacked.</p>
2507
     *
2508
     * @return bool <p>bool true on success, false otherwise.</p>
2509
     */
2510
    public function transact($callback): bool
2511
    {
2512
        try {
2513 3
            $beginTransaction = $this->beginTransaction();
2514 3
            if ($beginTransaction === false) {
2515 3
                $this->debug->displayError('Error: transact -> can not start transaction!', false);
2516
2517 3
                return false;
2518
            }
2519
2520 3
            $result = $callback($this);
2521 3
            if ($result === false) {
2522
                /** @noinspection ThrowRawExceptionInspection */
2523 3
                throw new \Exception('call_user_func [' . $callback . '] === false');
2524
            }
2525
2526 3
            return $this->commit();
2527 3
        } catch (\Exception $e) {
2528 3
            $this->rollback();
2529
2530 3
            return false;
2531
        }
2532
    }
2533
2534
    /**
2535
     * Execute a "update"-query.
2536
     *
2537
     * @param string       $table
2538
     * @param array        $data
2539
     * @param array|string $where
2540
     * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2541
     *
2542
     * @throws QueryException
2543
     *
2544
     * @return false|int <p>false on error</p>
2545
     */
2546
    public function update(string $table, array $data = [], $where = '1=1', string $databaseName = null)
2547
    {
2548
        // init
2549 21
        $table = \trim($table);
2550
2551 21
        if ($table === '') {
2552 3
            $this->debug->displayError('Invalid table name, table name in empty.', false);
2553
2554 3
            return false;
2555
        }
2556
2557 21
        if (\count($data) === 0) {
2558 6
            $this->debug->displayError('Invalid data for UPDATE, data is empty.', false);
2559
2560 6
            return false;
2561
        }
2562
2563
        // DEBUG
2564
        //var_dump($data);
2565
2566 21
        $SET = $this->_parseArrayPair($data);
2567
2568
        // DEBUG
2569
        //var_dump($SET);
2570
2571 21
        if (\is_string($where)) {
2572 6
            $WHERE = $this->escape($where, false);
2573 18
        } elseif (\is_array($where)) {
2574 15
            $WHERE = $this->_parseArrayPair($where, 'AND');
2575
        } else {
2576 3
            $WHERE = '';
2577
        }
2578
2579 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...
2580
            $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2581
        }
2582
2583 21
        $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET ${SET} WHERE (${WHERE})";
2584
2585 21
        return $this->query($sql);
2586
    }
2587
}
2588