Passed
Pull Request — master (#1572)
by Michael
09:45
created

XoopsMySQLDatabase::getAffectedRows()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
/**
3
 * MySQL access using MySQLi extension
4
 *
5
 * You may not change or alter any portion of this comment or credits
6
 * of supporting developers from this source code or any supporting source code
7
 * which is considered copyrighted (c) material of the original comment or credit authors.
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * @copyright       (c) 2000-2025 XOOPS Project (https://xoops.org)
13
 * @license             GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14
 * @package             class
15
 * @subpackage          database
16
 * @since               1.0.0
17
 * @author              Kazumi Ono <[email protected]>
18
 * @author              Rodney Fulk <[email protected]>
19
 */
20
defined('XOOPS_ROOT_PATH') || die('Restricted access');
21
22
include_once XOOPS_ROOT_PATH . '/class/database/database.php';
23
24
/**
25
 * Connection to a MySQL database using MySQLi extension
26
 */
27
abstract class XoopsMySQLDatabase extends XoopsDatabase
28
{
29
    /**
30
     * Database connection
31
     *
32
     * @var XoopsDatabase|mysqli
33
     */
34
    public $conn;
35
36
    /**
37
     * connect to the database
38
     *
39
     * @param bool $selectdb select the database now?
40
     * @return bool successful?
41
     */
42
    public function connect($selectdb = true)
43
    {
44
        if (!extension_loaded('mysqli')) {
45
            throw new \Exception('notrace:mysqli extension not loaded');
46
47
            return false;
0 ignored issues
show
Unused Code introduced by
return false is not 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...
48
        }
49
50
        $this->allowWebChanges = ($_SERVER['REQUEST_METHOD'] !== 'GET');
51
52
        if ($selectdb) {
53
            $dbname = constant('XOOPS_DB_NAME');
54
        } else {
55
            $dbname = '';
56
        }
57
        mysqli_report(MYSQLI_REPORT_OFF);
58
        if (XOOPS_DB_PCONNECT == 1) {
0 ignored issues
show
introduced by
The condition XOOPS_DB_PCONNECT == 1 is always false.
Loading history...
59
            $this->conn = new mysqli('p:' . XOOPS_DB_HOST, XOOPS_DB_USER, XOOPS_DB_PASS, $dbname);
60
        } else {
61
            $this->conn = new mysqli(XOOPS_DB_HOST, XOOPS_DB_USER, XOOPS_DB_PASS, $dbname);
62
        }
63
64
        // errno is 0 if connect was successful
65
        if (0 !== $this->conn->connect_errno) {
66
            return false;
67
        }
68
69
        if (defined('XOOPS_DB_CHARSET') && ('' !== XOOPS_DB_CHARSET)) {
0 ignored issues
show
introduced by
The condition '' !== XOOPS_DB_CHARSET is always false.
Loading history...
70
            // $this->queryF("SET NAMES '" . XOOPS_DB_CHARSET . "'");
71
            $this->conn->set_charset(XOOPS_DB_CHARSET);
72
        }
73
        $this->queryF('SET SQL_BIG_SELECTS = 1');
74
75
        return true;
76
    }
77
78
    /**
79
     * generate an ID for a new row
80
     *
81
     * This is for compatibility only. Will always return 0, because MySQL supports
82
     * autoincrement for primary keys.
83
     *
84
     * @param string $sequence name of the sequence from which to get the next ID
85
     * @return int always 0, because mysql has support for autoincrement
86
     */
87
    public function genId($sequence)
0 ignored issues
show
Unused Code introduced by
The parameter $sequence is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

87
    public function genId(/** @scrutinizer ignore-unused */ $sequence)

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

Loading history...
88
    {
89
        return 0; // will use auto_increment
90
    }
91
92
    /**
93
     * Get a result row as an enumerated array
94
     *
95
     * @param \mysqli_result $result
96
     *
97
     * @return array|false false on end of data
98
     */
99
    public function fetchRow($result)
100
    {
101
        $row = @mysqli_fetch_row($result);
102
        return $row ?? false;
103
    }
104
105
    /**
106
     * Fetch a result row as an associative array
107
     *
108
     * @param \mysqli_result $result
109
     *
110
     * @return array|false false on end of data
111
     */
112
    public function fetchArray($result)
113
    {
114
        $row = @mysqli_fetch_assoc($result);
115
        return $row ?? false;
116
117
    }
118
119
    /**
120
     * Fetch a result row as an associative array
121
     *
122
     * @param \mysqli_result $result
123
     *
124
     * @return array|false false on end of data
125
     */
126
    public function fetchBoth($result)
127
    {
128
        $row = @mysqli_fetch_array($result, MYSQLI_BOTH);
129
        return $row ?? false;
130
    }
131
132
    /**
133
     * XoopsMySQLDatabase::fetchObject()
134
     *
135
     * @param \mysqli_result $result
136
     * @return stdClass|false false on end of data
137
     */
138
    public function fetchObject($result)
139
    {
140
        $row = @mysqli_fetch_object($result);
141
        return $row ?? false;
142
    }
143
144
    /**
145
     * Get the ID generated from the previous INSERT operation
146
     *
147
     * @return int|string
148
     */
149
    public function getInsertId()
150
    {
151
        return mysqli_insert_id($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_insert_id() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

151
        return mysqli_insert_id(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
152
    }
153
154
    /**
155
     * Get number of rows in result
156
     *
157
     * @param \mysqli_result $result
158
     *
159
     * @return int
160
     */
161
    public function getRowsNum($result)
162
    {
163
        return (int)@mysqli_num_rows($result);
164
    }
165
166
    /**
167
     * Get number of affected rows
168
     *
169
     * @return int
170
     */
171
    public function getAffectedRows()
172
    {
173
        return (int)mysqli_affected_rows($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_affected_rows() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

173
        return (int)mysqli_affected_rows(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
174
    }
175
176
    /**
177
     * Close MySQL connection
178
     *
179
     * @return void
180
     */
181
    public function close()
182
    {
183
        mysqli_close($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_close() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

183
        mysqli_close(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
184
    }
185
186
    /**
187
     * will free all memory associated with the result identifier result.
188
     *
189
     * @param \mysqli_result $result result
190
     *
191
     * @return void
192
     */
193
    public function freeRecordSet($result)
194
    {
195
        mysqli_free_result($result);
196
    }
197
198
    /**
199
     * Returns the text of the error message from previous MySQL operation
200
     *
201
     * @return string Returns the error text from the last MySQL function, or '' (the empty string) if no error occurred.
202
     */
203
    public function error()
204
    {
205
        return @mysqli_error($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_error() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

205
        return @mysqli_error(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
206
    }
207
208
    /**
209
     * Returns the numerical value of the error message from previous MySQL operation
210
     *
211
     * @return int Returns the error number from the last MySQL function, or 0 (zero) if no error occurred.
212
     */
213
    public function errno()
214
    {
215
        return @mysqli_errno($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_errno() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

215
        return @mysqli_errno(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
216
    }
217
218
    /**
219
     * Returns escaped string text with single quotes around it to be safely stored in database
220
     *
221
     * @param string $str unescaped string text
222
     * @return string escaped string text with single quotes around
223
     * @deprecated : delegate to exec().
224
     */
225
    public function quoteString($str)
226
    {
227
228
        if (is_object($this->logger)) {
229
            $this->logger->addDeprecated(__METHOD__ . " is deprecated since XOOPS 2.5.12, please use 'quote()' instead.");
230
        }
231
232
        return $this->quote($str);
233
    }
234
235
    /**
236
     * Quotes a string for use in a query.
237
     *
238
     * @param string $string string to quote/escape for use in query
239
     *
240
     * @return string
241
     */
242
    public function quote($string)
243
    {
244
        $quoted = $this->escape($string);
245
        return "'{$quoted}'";
246
    }
247
248
    /**
249
     * Escapes a string for use in a query. Does not add surrounding quotes.
250
     *
251
     * @param string $string string to escape
252
     *
253
     * @return string
254
     */
255
    public function escape($string)
256
    {
257
        return mysqli_real_escape_string($this->conn, (string) $string);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_real_escape_string() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

257
        return mysqli_real_escape_string(/** @scrutinizer ignore-type */ $this->conn, (string) $string);
Loading history...
258
    }
259
260
    /**
261
     * perform a query on the database
262
     *
263
     * @param string $sql   a valid MySQL query
264
     * @param int    $limit number of records to return
265
     * @param int    $start offset of first record to return
266
     * @return mysqli_result|bool query result or FALSE if successful
267
     *                      or TRUE if successful and no result
268
     */
269
    public function queryF($sql, $limit = 0, $start = 0)
270
    {
271
        if (!empty($limit)) {
272
            if (empty($start)) {
273
                $start = 0;
274
            }
275
            $sql .= ' LIMIT ' . (int)$start . ', ' . (int)$limit;
276
        }
277
        $this->logger->startTime('query_time');
278
        $result = mysqli_query($this->conn, $sql);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_query() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

278
        $result = mysqli_query(/** @scrutinizer ignore-type */ $this->conn, $sql);
Loading history...
279
        $this->logger->stopTime('query_time');
280
        $t = $this->logger->dumpTime('query_time', true);
281
282
        if ($result) {
283
            $this->logger->addQuery($sql, null, null, $t);
284
            return $result;             // mysqli_result for SELECT, true for writes
285
        } else {
286
            $this->logger->addQuery($sql, $this->error(), $this->errno(), $t);
287
            return false;
288
        }
289
    }
290
291
    /**
292
     * perform a query
293
     *
294
     * This method is empty and does nothing! It should therefore only be
295
     * used if nothing is exactly what you want done! ;-)
296
     *
297
     * @param string $sql   a valid MySQL query
298
     * @param int|null    $limit number of records to return
299
     * @param int|null    $start offset of first record to return
300
     *
301
     * @return \mysqli_result|bool query result or FALSE if successful
302
     *                      or TRUE if successful and no result
303
     */
304
    public function query(string $sql, ?int $limit = null, ?int $start = null)
305
    {
306
        // Optional: dev-only guard to catch misuse (writes via query()).
307
        if (defined('XOOPS_DB_STRICT') && XOOPS_DB_STRICT) {
0 ignored issues
show
Bug introduced by
The constant XOOPS_DB_STRICT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
308
            // Treat these as read-like; everything else is suspicious in query().
309
            if (!preg_match('/^\s*(SELECT|WITH|SHOW|DESCRIBE|EXPLAIN)\b/i', $sql)) {
310
                if (is_object($this->logger)) {
311
                    $this->logger->warning('query() called with a mutating statement; use exec() instead');
312
                } else {
313
                    trigger_error('query() called with a mutating statement; use exec() instead', E_USER_WARNING);
314
                }
315
            }
316
        }
317
318
        // Append pagination if requested (null = no pagination)
319
        if ($limit !== null) {
320
            $start = max(0, $start ?? 0);
321
            // MySQL supports both "LIMIT count OFFSET start" and "LIMIT start, count".
322
            // Using the former improves cross-driver readability.
323
            $sql .= ' LIMIT ' . (int)$limit . ' OFFSET ' . $start;
324
        }
325
326
        // No error suppression: handle failures explicitly
327
        $res = \mysqli_query($this->conn, $sql);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_query() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

327
        $res = \mysqli_query(/** @scrutinizer ignore-type */ $this->conn, $sql);
Loading history...
328
329
        if ($res === false) {
330
            // Uniform SQL logging (time already measured above)
331
            $this->logger->addQuery($sql, $this->error(), $this->errno(), $t);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $t seems to be never defined.
Loading history...
332
333
            // Optional extra breadcrumb (safe on 2.5.x)
334
            if (is_object($this->logger)) {
335
                $this->logger->addExtra('DB', 'Query failed: errno ' . $this->errno() . ' - ' . $this->error());
336
            }
337
            return false;
338
        }
339
340
        // For proper SELECT-like statements, $res is a mysqli_result.
341
        // If someone sent a write by mistake, mysqli_query() returns true; we pass it through.
342
        return $res;
343
    }
344
345
346
    /**
347
     * perform queries from SQL dump file in a batch
348
     *
349
     * @param string $file file path to an SQL dump file
350
     * @return bool FALSE if failed reading SQL file or TRUE if the file has been read and queries executed
351
     */
352
    public function queryFromFile($file)
353
    {
354
        if (false !== ($fp = fopen($file, 'r'))) {
355
            include_once XOOPS_ROOT_PATH . '/class/database/sqlutility.php';
356
            $sql_queries = trim(fread($fp, filesize($file)));
357
            SqlUtility::splitMySqlFile($pieces, $sql_queries);
358
            foreach ($pieces as $query) {
359
                // [0] contains the prefixed query
360
                // [4] contains unprefixed table name
361
                $prefixed_query = SqlUtility::prefixQuery(trim($query), $this->prefix());
362
                if ($prefixed_query != false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
363
                    $this->query($prefixed_query[0]);
364
                }
365
            }
366
367
            return true;
368
        }
369
370
        return false;
371
    }
372
373
    /**
374
     * Get field name
375
     *
376
     * @param \mysqli_result $result query result
377
     * @param int           $offset numerical field index
378
     *
379
     * @return string
380
     */
381
    public function getFieldName($result, $offset)
382
    {
383
        return $result->fetch_field_direct($offset)->name;
384
    }
385
386
    /**
387
     * Get field type
388
     *
389
     * @param \mysqli_result $result query result
390
     * @param int           $offset numerical field index
391
     *
392
     * @return string
393
     */
394
    public function getFieldType($result, $offset)
395
    {
396
        $typecode = $result->fetch_field_direct($offset)->type;
397
        switch ($typecode) {
398
            case MYSQLI_TYPE_DECIMAL:
399
            case MYSQLI_TYPE_NEWDECIMAL:
400
                $type = 'decimal';
401
                break;
402
            case MYSQLI_TYPE_BIT:
403
                $type = 'bit';
404
                break;
405
            case MYSQLI_TYPE_TINY:
406
            case MYSQLI_TYPE_CHAR:
407
                $type = 'tinyint';
408
                break;
409
            case MYSQLI_TYPE_SHORT:
410
                $type = 'smallint';
411
                break;
412
            case MYSQLI_TYPE_LONG:
413
                $type = 'int';
414
                break;
415
            case MYSQLI_TYPE_FLOAT:
416
                $type = 'float';
417
                break;
418
            case MYSQLI_TYPE_DOUBLE:
419
                $type = 'double';
420
                break;
421
            case MYSQLI_TYPE_NULL:
422
                $type = 'NULL';
423
                break;
424
            case MYSQLI_TYPE_TIMESTAMP:
425
                $type = 'timestamp';
426
                break;
427
            case MYSQLI_TYPE_LONGLONG:
428
                $type = 'bigint';
429
                break;
430
            case MYSQLI_TYPE_INT24:
431
                $type = 'mediumint';
432
                break;
433
            case MYSQLI_TYPE_NEWDATE:
434
            case MYSQLI_TYPE_DATE:
435
                $type = 'date';
436
                break;
437
            case MYSQLI_TYPE_TIME:
438
                $type = 'time';
439
                break;
440
            case MYSQLI_TYPE_DATETIME:
441
                $type = 'datetime';
442
                break;
443
            case MYSQLI_TYPE_YEAR:
444
                $type = 'year';
445
                break;
446
            case MYSQLI_TYPE_INTERVAL:
447
                $type = 'interval';
448
                break;
449
            case MYSQLI_TYPE_ENUM:
450
                $type = 'enum';
451
                break;
452
            case MYSQLI_TYPE_SET:
453
                $type = 'set';
454
                break;
455
            case MYSQLI_TYPE_TINY_BLOB:
456
                $type = 'tinyblob';
457
                break;
458
            case MYSQLI_TYPE_MEDIUM_BLOB:
459
                $type = 'mediumblob';
460
                break;
461
            case MYSQLI_TYPE_LONG_BLOB:
462
                $type = 'longblob';
463
                break;
464
            case MYSQLI_TYPE_BLOB:
465
                $type = 'blob';
466
                break;
467
            case MYSQLI_TYPE_VAR_STRING:
468
                $type = 'varchar';
469
                break;
470
            case MYSQLI_TYPE_STRING:
471
                $type = 'char';
472
                break;
473
            case MYSQLI_TYPE_GEOMETRY:
474
                $type = 'geometry';
475
                break;
476
            default:
477
                $type = 'unknown';
478
                break;
479
        }
480
481
        return $type;
482
    }
483
484
    /**
485
     * Get number of fields in result
486
     *
487
     * @param \mysqli_result $result query result
488
     *
489
     * @return int
490
     */
491
    public function getFieldsNum($result)
492
    {
493
        return mysqli_num_fields($result);
494
    }
495
496
    /**
497
     * getServerVersion get version of the mysql server
498
     *
499
     * @return string
500
     */
501
    public function getServerVersion()
502
    {
503
        return mysqli_get_server_info($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_get_server_info() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

503
        return mysqli_get_server_info(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
504
    }
505
506
    /**
507
     * Test the passed result to determine if it is a valid result set
508
     *
509
     * @param mixed $result value to test
510
     *
511
     * @return bool true if $result is a database result set, otherwise false
512
     */
513
    public function isResultSet($result)
514
    {
515
        return is_a($result, 'mysqli_result');
516
    }
517
518
    public function exec(string $sql): bool
519
    {
520
        // Optional dev-only guard: flag accidental read statements passed to exec()
521
        if (defined('XOOPS_DB_STRICT') && XOOPS_DB_STRICT) {
0 ignored issues
show
Bug introduced by
The constant XOOPS_DB_STRICT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
522
            if (preg_match('/^\s*(?:SELECT|WITH|SHOW|DESCRIBE|EXPLAIN)\b/i', $sql)) {
523
                if (is_object($this->logger)) {
524
                    $this->logger->addExtra('DB', 'exec() called with a read-only statement');
525
                }
526
                trigger_error('exec() called with a read-only statement', E_USER_WARNING);
527
            }
528
        }
529
530
        // No error suppression: let us see and handle failures explicitly
531
        $res = \mysqli_query($this->conn, $sql);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_query() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

531
        $res = \mysqli_query(/** @scrutinizer ignore-type */ $this->conn, $sql);
Loading history...
532
533
        if ($res === false) {
534
            if (is_object($this->logger)) {
535
                $this->logger->addQuery($sql, $this->error(), $this->errno(), $t);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $t seems to be never defined.
Loading history...
536
537
                // Optional: keep only if you want a human-friendly breadcrumb in the debug pane
538
                // Consider gating it behind XOOPS_DB_STRICT to reduce noise in production.
539
                if (defined('XOOPS_DB_STRICT') && XOOPS_DB_STRICT) {
540
                    $this->logger->addExtra('DB', 'Exec failed: errno ' . $this->errno() . ' - ' . $this->error());
541
            }
542
        }
543
            return false;
544
545
        // If someone passes a SELECT by mistake, mysqli returns a result; free it and treat as success
546
        if ($res instanceof \mysqli_result) {
0 ignored issues
show
Unused Code introduced by
IfNode is not 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...
547
                \mysqli_free_result($res);
548
        }
549
550
        return true; // mysqli_query() returned true or a freed result set
551
    }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 533 is false. This is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
552
553
    }
554
}
555
556
/**
557
 * Safe Connection to a MySQL database.
558
 *
559
 * Delegates to parent; signature matches parent for LSP.
560
 */
561
class XoopsMySQLDatabaseSafe extends XoopsMySQLDatabase
562
{
563
    /**
564
     * perform a query on the database
565
     *
566
     * @param string $sql   a valid MySQL query
567
     * @param int    $limit number of records to return
568
     * @param int    $start offset of first record to return
569
     * @return mysqli_result|bool query result or FALSE if successful
570
     *                      or TRUE if successful and no result
571
     */
572
    public function query($sql, $limit = 0, $start = 0)
573
    {
574
        return parent::query($sql, $limit ?: null, $start ?: null);
575
    }
576
}
577
578
/**
579
 * Read-Only connection to a MySQL database.
580
 *
581
 * This class allows only SELECT queries to be performed through its
582
 * {@link query()} method for security reasons.
583
 *
584
 * @author              Kazumi Ono <[email protected]>
585
 * @copyright       (c) 2000-2025 XOOPS Project (https://xoops.org)
586
 * @package             class
587
 * @subpackage          database
588
 */
589
class XoopsMySQLDatabaseProxy extends XoopsMySQLDatabase
590
{
591
    /**
592
     * perform a query on the database
593
     *
594
     * this method allows only SELECT queries for safety.
595
     *
596
     * @param string $sql   a valid MySQL query
597
     * @param int    $limit number of records to return
598
     * @param int    $start offset of first record to return
599
     *
600
     * @return mysqli_result|bool query result or FALSE if successful
601
     *                      or TRUE if successful and no result
602
     */
603
    public function query(string $sql, ?int $limit = null, ?int $start = null)
604
    {
605
        $sql = ltrim($sql);
606
        if (!$this->allowWebChanges && stripos($sql, 'select') !== 0) {
607
            trigger_error('Database updates are not allowed during processing of a GET request', E_USER_WARNING);
608
609
            return false;
610
        }
611
        // Execute via queryF() to preserve legacy path (and LIMIT semantics)
612
        if ($limit !== null) {
613
            $start = max(0, $start ?? 0);
614
            return $this->queryF($sql, (int)$limit, (int)$start);
615
    }
616
        return $this->queryF($sql);
617
    }
618
619
}
620