DBCore::doSelectQuery()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Asymptix\db;
4
5
use Asymptix\core\Tools;
6
7
/**
8
 * Core database functionality.
9
 *
10
 * @category Asymptix PHP Framework
11
 * @author Dmytro Zarezenko <[email protected]>
12
 * @copyright (c) 2009 - 2017, Dmytro Zarezenko
13
 *
14
 * @git https://github.com/Asymptix/Framework
15
 * @license http://opensource.org/licenses/MIT
16
 */
17
class DBCore {
18
    /**
19
     * An array containing all the opened connections.
20
     *
21
     * @var array
22
     */
23
    protected $connections = [];
24
25
    /**
26
     * The incremented index of connections.
27
     *
28
     * @var int
29
     */
30
    protected $index = 0;
31
32
    /**
33
     * Current connection index.
34
     *
35
     * @var int
36
     */
37
    protected $currIndex = 0;
38
39
    /**
40
     * Instance of a class.
41
     *
42
     * @var DBCore
43
     */
44
    protected static $instance;
45
46
    /**
47
     * Returns an instance of this class.
48
     *
49
     * @return DBCore
50
     */
51
    public static function getInstance() {
52
        if (!isset(self::$instance)) {
53
            self::$instance = new self();
54
        }
55
56
        return self::$instance;
57
    }
58
59
    /**
60
     * Reset the internal static instance.
61
     */
62
    public static function resetInstance() {
63
        if (self::$instance) {
64
            self::$instance->reset();
65
            self::$instance = null;
66
        }
67
    }
68
69
    /**
70
     * Reset this instance of the manager.
71
     */
72
    public function reset() {
73
        foreach ($this->connections as $conn) {
74
            $conn->close();
75
        }
76
        $this->connections = [];
77
        $this->index = 0;
78
        $this->currIndex = 0;
79
    }
80
81
    /**
82
     * Seves a new connection to DBCore->connections.
83
     *
84
     * @param mysqli Object $connResource An object which represents the connection to a MySQL Server.
85
     * @param string $connName Name of the connection, if empty numeric key is used.
86
     *
87
     * @throws DBCoreException If trying to save a connection with an existing name.
88
     */
89
    public static function connection($connResource = null, $connName = null) {
90
        if ($connResource == null) {
91
            return self::getInstance()->getCurrentConnection();
92
        }
93
        self::getInstance()->openConnection($connResource, $connName);
94
    }
95
96
    /**
97
     * Seves a new connection to DBCore->connections.
98
     *
99
     * @param mysqli Object $connResource An object which represents the connection to a MySQL Server.
100
     * @param string $connName Name of the connection, if empty numeric key is used.
101
     *
102
     * @throws DBCoreException If trying to save a connection with an existing name.
103
     */
104
    public function openConnection($connResource, $connName = null) {
105
        if ($connName !== null) {
106
            $connName = (string)$connName;
107
            if (isset($this->connections[$connName])) {
108
                throw new DBCoreException("You trying to save a connection with an existing name");
109
            }
110
        } else {
111
            $connName = $this->index;
112
            $this->index++;
113
        }
114
115
        $this->connections[$connName] = $connResource;
116
    }
117
118
    /**
119
     * Get the connection instance for the passed name.
120
     *
121
     * @param string $connName Name of the connection, if empty numeric key is used.
122
     *
123
     * @return mysqli Object
124
     *
125
     * @throws DBCoreException If trying to get a non-existent connection.
126
     */
127
    public function getConnection($connName) {
128
        if (!isset($this->connections[$connName])) {
129
            throw new DBCoreException('Unknown connection: ' . $connName);
130
        }
131
132
        return $this->connections[$connName];
133
    }
134
135
    /**
136
     * Get the name of the passed connection instance.
137
     *
138
     * @param mysqli Object $connResource Connection object to be searched for.
139
     *
140
     * @return string The name of the connection.
141
     */
142
    public function getConnectionName($connResource) {
143
        return array_search($connResource, $this->connections, true);
144
    }
145
146
    /**
147
     * Closes the specified connection.
148
     *
149
     * @param mixed $connection Connection object or its name.
150
     */
151
    public function closeConnection($connection) {
152
        $key = false;
153
        if (Tools::isObject($connection)) {
154
            $connection->close();
155
            $key = $this->getConnectionName($connection);
156
        } elseif (is_string($connection)) {
157
            $key = $connection;
158
        }
159
160
        if ($key !== false) {
161
            unset($this->connections[$key]);
162
163
            if ($key === $this->currIndex) {
164
                $key = key($this->connections);
165
                $this->currIndex = ($key !== null) ? $key : 0;
166
            }
167
        }
168
169
        unset($connection);
170
    }
171
172
    /**
173
     * Returns all opened connections.
174
     *
175
     * @return array
176
     */
177
    public function getConnections() {
178
        return $this->connections;
179
    }
180
181
    /**
182
     * Sets the current connection to $key.
183
     *
184
     * @param mixed $key The connection key
185
     *
186
     * @throws DBCoreException
187
     */
188
    public function setCurrentConnection($key) {
189
        if (!$this->contains($key)) {
190
            throw new DBCoreException("Connection key '$key' does not exist.");
191
        }
192
        $this->currIndex = $key;
193
    }
194
195
    /**
196
     * Whether or not the DBCore contains specified connection.
197
     *
198
     * @param mixed $key The connection key
199
     *
200
     * @return bool
201
     */
202
    public function contains($key) {
203
        return isset($this->connections[$key]);
204
    }
205
206
    /**
207
     * Returns the number of opened connections.
208
     *
209
     * @return int
210
     */
211
    public function count() {
212
        return count($this->connections);
213
    }
214
215
    /**
216
     * Returns an ArrayIterator that iterates through all connections.
217
     *
218
     * @return ArrayIterator
219
     */
220
    public function getIterator() {
221
        return new ArrayIterator($this->connections);
222
    }
223
224
    /**
225
     * Get the current connection instance.
226
     *
227
     * @throws DBCoreException If there are no open connections
228
     *
229
     * @return mysqli Object
230
     */
231
    public function getCurrentConnection() {
232
        $key = $this->currIndex;
233
        if (!isset($this->connections[$key])) {
234
            throw new DBCoreException('There is no open connection');
235
        }
236
237
        return $this->connections[$key];
238
    }
239
240
    /**
241
     * Check database errors.
242
     *
243
     * @param object $dbObj
244
     */
245
    private static function checkDbError($dbObj) {
246
        if ($dbObj->error != "") {
247
            throw new DBCoreException($dbObj->error);
248
        }
249
    }
250
251
    /**
252
     * Bind parameters to the statment with dynamic number of parameters.
253
     *
254
     * @param resource $stmt Statement.
255
     * @param string $types Types string.
256
     * @param array $params Parameters.
257
     */
258
    private static function bindParameters($stmt, $types, $params) {
259
        $args   = [];
260
        $args[] = $types;
261
262
        foreach ($params as &$param) {
263
            $args[] = &$param;
264
        }
265
        call_user_func_array([$stmt, 'bind_param'], $args);
266
    }
267
268
    /**
269
     * Return parameters from the statment with dynamic number of parameters.
270
     *
271
     * @param resource $stmt Statement.
272
     */
273
    public static function bindResults($stmt) {
274
        $resultSet = [];
275
        $metaData = $stmt->result_metadata();
0 ignored issues
show
Bug introduced by
The method result_metadata cannot be called on $stmt (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
276
        $fieldsCounter = 0;
277
        while ($field = $metaData->fetch_field()) {
278
            if (!isset($resultSet[$field->table])) {
279
                $resultSet[$field->table] = [];
280
            }
281
            $resultSet[$field->table][$field->name] = $fieldsCounter++;
282
            $parameterName = "variable" . $fieldsCounter; //$field->name;
283
            $$parameterName = null;
284
            $parameters[] = &$$parameterName;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
285
        }
286
        call_user_func_array([$stmt, 'bind_result'], $parameters);
0 ignored issues
show
Bug introduced by
The variable $parameters 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...
287
        if ($stmt->fetch()) {
0 ignored issues
show
Bug introduced by
The method fetch cannot be called on $stmt (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
288
            foreach ($resultSet as &$tableResult) {
289
                foreach ($tableResult as &$fieldValue) {
290
                    $fieldValue = $parameters[$fieldValue];
291
                }
292
            }
293
294
            return $resultSet;
295
        }
296
        self::checkDbError($stmt);
0 ignored issues
show
Documentation introduced by
$stmt is of type resource, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
297
298
        return null;
299
    }
300
301
    /**
302
     * Execute DB SQL queries using Prepared Statements.
303
     *
304
     * @param mixed $query SQL query template string or DBPreparedQuery object
305
     *           if single parameter.
306
     * @param string $types Types string (ex: "isdb").
307
     * @param array $params Parameters in the same order like types string.
308
     *
309
     * @return mixed Statement object or FALSE if an error occurred.
310
     */
311
    private static function doQuery($query, $types = "", $params = []) {
312 View Code Duplication
        if (!Tools::isInstanceOf($query, new DBPreparedQuery())) {
0 ignored issues
show
Documentation introduced by
new \Asymptix\db\DBPreparedQuery() is of type object<Asymptix\db\DBPreparedQuery>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
313
            $dbQuery = new DBPreparedQuery($query, $types, $params);
314
        } else {
315
            $dbQuery = $query;
316
        }
317
318
        $stmt = self::connection()->prepare($dbQuery->query);
319
        self::checkDbError(self::connection());
0 ignored issues
show
Bug introduced by
It seems like self::connection() can be null; however, checkDbError() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
320
321
        if ($dbQuery->isBindable()) {
322
            if ($dbQuery->isValid()) {
323
                self::bindParameters($stmt, $dbQuery->types, $dbQuery->params);
324
            } else {
325
                throw new DBCoreException(
326
                    "Number of types is not equal parameters number or types string is invalid"
327
                );
328
            }
329
        }
330
331
        $stmt->execute();
332
        self::checkDbError($stmt);
333
334
        return $stmt;
335
    }
336
337
    /**
338
     * Execute DB SQL queries using Prepared Statements.
339
     *
340
     * @param mixed $query SQL query template string or DBPreparedQuery object
341
     *           if single parameter.
342
     * @param string $types Types string (ex: "isdb").
343
     * @param array $params Parameters in the same order like types string.
344
     *
345
     * @return mixed Statement object or FALSE if an error occurred.
346
     */
347
    public static function query($query, $types = "", $params = []) {
348
        return (new DBPreparedQuery($query, $types, $params))->go();
349
    }
350
351
    /**
352
     * Execute update DB SQL queries using Prepared Statements.
353
     *
354
     * @param string $query SQL query template string or DBPreparedQuery object
355
     *           if single parameter.
356
     * @param string $types Types string.
357
     * @param array $params Parameters.
358
     *
359
     * @return int Returns the number of affected rows on success and
360
     *           -1 if the last query failed.
361
     */
362
    public static function doUpdateQuery($query, $types = "", $params = []) {
363 View Code Duplication
        if (!Tools::isInstanceOf($query, new DBPreparedQuery())) {
0 ignored issues
show
Documentation introduced by
new \Asymptix\db\DBPreparedQuery() is of type object<Asymptix\db\DBPreparedQuery>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
364
            $dbQuery = new DBPreparedQuery($query, $types, $params);
365
        } else {
366
            $dbQuery = $query;
367
        }
368
        $stmt = self::doQuery($dbQuery);
369
370
        switch ($dbQuery->getType()) {
371
            case (DBQueryType::INSERT):
372
                $result = self::connection()->insert_id;
373
                break;
374
            case (DBQueryType::UPDATE):
375
                $result = self::connection()->affected_rows;
376
                break;
377
            default:
378
                $result = self::connection()->affected_rows;
379
        }
380
        $stmt->close();
381
382
        return $result;
383
    }
384
385
    /**
386
     * Execute select DB SQL queries using Prepared Statements.
387
     *
388
     * @param mixed $query SQL query template string or DBPreparedQuery object
389
     *           if single parameter.
390
     * @param string $types Types string (ex: "isdb").
391
     * @param array $params Parameters in the same order like types string.
392
     *
393
     * @return mixed Statement object or FALSE if an error occurred.
394
     */
395
    public static function doSelectQuery($query, $types = "", $params = []) {
396
        $stmt = self::doQuery($query, $types, $params);
397
398
        $stmt->store_result();
399
        self::checkDbError($stmt);
400
401
        return $stmt;
402
    }
403
404
    /**
405
     * Returns list of database table fields.
406
     *
407
     * @param string $tableName Name of the table.
408
     * @return array<string> List of the database table fields (syntax: array[fieldName] = fieldType)
409
     */
410
    public static function getTableFieldsList($tableName) {
411
        if (!empty($tableName)) {
412
            $query = "SHOW FULL COLUMNS FROM " . $tableName;
413
            $stmt = self::doSelectQuery($query);
414
            if ($stmt !== false) {
415
                $stmt->bind_result(
416
                    $field, $type, $collation, $null, $key, $default, $extra, $privileges, $comment
0 ignored issues
show
Bug introduced by
The variable $field does not exist. Did you forget to declare it?

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

Loading history...
Bug introduced by
The variable $type does not exist. Did you forget to declare it?

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

Loading history...
Bug introduced by
The variable $collation does not exist. Did you forget to declare it?

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

Loading history...
Bug introduced by
The variable $null does not exist. Did you forget to declare it?

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

Loading history...
Bug introduced by
The variable $key does not exist. Did you forget to declare it?

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

Loading history...
Bug introduced by
The variable $default does not exist. Did you forget to declare it?

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

Loading history...
Bug introduced by
The variable $extra does not exist. Did you forget to declare it?

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

Loading history...
Bug introduced by
The variable $privileges does not exist. Did you forget to declare it?

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

Loading history...
Bug introduced by
The variable $comment does not exist. Did you forget to declare it?

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

Loading history...
417
                );
418
419
                $fieldsList = [];
420
                while ($stmt->fetch()) {
421
                    $fieldsList[$field] = [
422
                        'type' => $type,
423
                        'collation' => $collation,
424
                        'null' => $null,
425
                        'key' => $key,
426
                        'default' => $default,
427
                        'extra' => $extra,
428
                        'privileges' => $privileges,
429
                        'comment' => $comment
430
                    ];
431
                }
432
                $stmt->close();
433
434
                return $fieldsList;
435
            }
436
        }
437
438
        return [];
439
    }
440
441
    /**
442
     * Returns printable SQL field value for table fields list generator.
443
     *
444
     * @param string $type SQL type of the field.
445
     * @param mixed $value Field value.
446
     *
447
     * @return string
448
     */
449
    private static function getPrintableSQLValue($type, $value) {
450
        if (strpos($type, "varchar") === 0
451
         || strpos($type, "text") === 0
452
         || strpos($type, "longtext") === 0
453
         || strpos($type, "enum") === 0
454
         || strpos($type, "char") === 0
455
         || strpos($type, "datetime") === 0
456
         || strpos($type, "timestamp") === 0
457
         || strpos($type, "date") === 0) {
458
            return ('"' . $value . '"');
459
        } elseif (strpos($type, "int") === 0
460
         || strpos($type, "tinyint") === 0
461
         || strpos($type, "smallint") === 0
462
         || strpos($type, "mediumint") === 0
463
         || strpos($type, "bigint") === 0) {
464
            if (!empty($value)) {
465
                return $value;
466
            }
467
468
            return "0";
469
        } elseif (strpos($type, "float") === 0
470
         || strpos($type, "double") === 0
471
         || strpos($type, "decimal") === 0) {
472
            if (!empty($value)) {
473
                return $value;
474
            }
475
476
            return "0.0";
477
        }
478
479
        return $value;
480
    }
481
482
    /**
483
     * Returns printable field description string for table fields list generator.
484
     *
485
     * @param string $field Field name.
486
     * @param array $attributes List of field attributes.
487
     *
488
     * @return string
489
     */
490
    public static function getPrintableFieldString($field, $attributes) {
491
        $extra = trim($attributes['extra']);
492
        $comment = trim($attributes['comment']);
493
494
        $fieldStr = "'" . $field . "' => ";
495
        if ($attributes['null'] === 'YES' && is_null($attributes['default'])) {
496
            $fieldStr.= "null";
497
        } else {
498
            $fieldStr.= self::getPrintableSQLValue($attributes['type'], $attributes['default']);
499
        }
500
        $fieldStr.= ", // " . $attributes['type'] .
501
            ", " . (($attributes['null'] == "NO") ? "not null" : "null")
502
            . ", default '" . $attributes['default'] . "'" .
503
            ($extra ? ", " . $extra : "") .
504
            ($comment ? " (" . $comment . ")" : "") . "\n";
505
506
        return $fieldStr;
507
    }
508
509
    /**
510
     * Outputs comfortable for Bean Class creation list of table fields.
511
     *
512
     * @param string $tableName Name of the Db table.
513
     */
514
    public static function displayTableFieldsList($tableName) {
515
        print("<pre>");
516
        if (!empty($tableName)) {
517
            $fieldsList = self::getTableFieldsList($tableName);
518
            if (!empty($fieldsList)) {
519
                foreach ($fieldsList as $field => $attributes) {
520
                    print(self::getPrintableFieldString($field, $attributes));
521
                }
522
            }
523
        }
524
        print("</pre>");
525
    }
526
527
    /**
528
     * Returns list of fields values with default indexes.
529
     *
530
     * @param array<mixed> $fieldsList List of the table fields (syntax: array[fieldName] = fieldValue)
531
     * @param string $idFieldName Name of the primary key field.
532
     * @return array<mixed>
533
     */
534
    public static function createValuesList($fieldsList, $idFieldName = "") {
535
        $valuesList = [];
536
        foreach ($fieldsList as $fieldName => $fieldValue) {
537
            if ($fieldName != $idFieldName) {
538
                $valuesList[] = $fieldValue;
539
            }
540
        }
541
542
        return $valuesList;
543
    }
544
545
    /**
546
     * Executes SQL INSERT query to the database.
547
     *
548
     * @param DBObject $dbObject DBObject to insert.
549
     * @param bool $ignore Ignore unique indexes or not.
550
     * @param bool $debug Debug mode flag.
551
     *
552
     * @return int Insertion ID (primary key value) or null on debug.
553
     */
554
    public static function insertDBObject($dbObject, $ignore = false, $debug = false) {
555
        $fieldsList = $dbObject->getFieldsList();
556
        $idFieldName = $dbObject->getIdFieldName();
557
558
        if (Tools::isInteger($fieldsList[$idFieldName])) {
559
            $query = "INSERT " . ($ignore ? 'IGNORE' : 'INTO') . " " . $dbObject->getTableName() . "
560
                          SET " . DBPreparedQuery::sqlQMValuesString($fieldsList, $idFieldName);
561
            $typesString = DBPreparedQuery::sqlTypesString($fieldsList, $idFieldName);
562
            $valuesList = self::createValuesList($fieldsList, $idFieldName);
563
        } else {
564
            $query = "INSERT " . ($ignore ? 'IGNORE' : 'INTO') . " " . $dbObject->getTableName() . "
565
                          SET " . DBPreparedQuery::sqlQMValuesString($fieldsList);
566
            $typesString = DBPreparedQuery::sqlTypesString($fieldsList);
567
            $valuesList = self::createValuesList($fieldsList);
568
        }
569
570
        if ($debug) {
571
            DBQuery::showQueryDebugInfo($query, $typesString, $valuesList);
572
573
            return null;
574
        }
575
        self::doUpdateQuery($query, $typesString, $valuesList);
576
577
        return (self::connection()->insert_id);
578
    }
579
580
    /**
581
     * Executes SQL UPDATE query to the database.
582
     *
583
     * @param DBObject $dbObject DBObject to update.
584
     * @param bool $debug Debug mode flag.
585
     *
586
     * @return int Returns the number of affected rows on success, and -1 if
587
     *           the last query failed.
588
     */
589
    public static function updateDBObject($dbObject, $debug = false) {
590
        $fieldsList = $dbObject->getFieldsList();
591
        $idFieldName = $dbObject->getIdFieldName();
592
593
        $query = "UPDATE " . $dbObject->getTableName() . "
594
                  SET " . DBPreparedQuery::sqlQMValuesString($fieldsList, $idFieldName) . "
595
                  WHERE " . $idFieldName . " = ?
596
                  LIMIT 1";
597
        $typesString = DBPreparedQuery::sqlTypesString($fieldsList, $idFieldName);
598
        if (Tools::isInteger($fieldsList[$idFieldName])) {
599
            $typesString.= "i";
600
        } else {
601
            $typesString.= "s";
602
        }
603
        $valuesList = self::createValuesList($fieldsList, $idFieldName);
604
        $valuesList[] = $dbObject->getId();
605
606
        if ($debug) {
607
            DBQuery::showQueryDebugInfo($query, $typesString, $valuesList);
608
        } else {
609
            return self::doUpdateQuery($query, $typesString, $valuesList);
610
        }
611
    }
612
613
    /**
614
     * Executes SQL DELETE query to the database.
615
     *
616
     * @param DBObject $dbObject DBObject to delete.
617
     *
618
     * @return int Returns the number of affected rows on success, and -1 if
619
     *           the last query failed.
620
     */
621
    public static function deleteDBObject($dbObject) {
622
        if (!empty($dbObject) && is_object($dbObject)) {
623
            $query = "DELETE FROM " . $dbObject->getTableName() .
624
                     " WHERE " . $dbObject->getIdFieldName() . " = ? LIMIT 1";
625
626
            $typesString = "s";
627
            if (Tools::isInteger($dbObject->getId())) {
628
                $typesString = "i";
629
            }
630
631
            return self::doUpdateQuery(
632
                $query, $typesString, [$dbObject->getId()]
633
            );
634
        }
635
636
        return false;
637
    }
638
639
    /**
640
     * Returns DBObject from ResultSet.
641
     *
642
     * @param DBObject $dbObject
643
     * @param array $resultSet Associated by table names arrays of selected
644
     *           fields.
645
     *
646
     * @return DBObject
647
     */
648
    public static function selectDBObjectFromResultSet($dbObject, $resultSet) {
649
        $dbObject->setFieldsValues($resultSet[$dbObject->getTableName()]);
650
651
        return $dbObject;
652
    }
653
654
    /**
655
     * Returns DB object by database query statement.
656
     *
657
     * @param resource $stmt Database query statement.
658
     * @param string $className Name of the DB object class.
659
     * @return DBObject
660
     */
661
    public static function selectDBObjectFromStatement($stmt, $className) {
662
        if (is_object($className)) {
663
            $className = get_class($className);
664
        }
665
666
        if ($stmt->num_rows == 1) {
667
            $resultSet = self::bindResults($stmt);
668
            $dbObject = new $className();
669
            self::selectDBObjectFromResultSet($dbObject, $resultSet);
0 ignored issues
show
Bug introduced by
It seems like $resultSet defined by self::bindResults($stmt) on line 667 can also be of type null; however, Asymptix\db\DBCore::selectDBObjectFromResultSet() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
670
671
            if (!is_null($dbObject) && is_object($dbObject) && $dbObject->getId()) {
672
                return $dbObject;
673
            } else {
674
                return null;
675
            }
676
        } elseif ($stmt->num_rows > 1) {
677
            throw new DBCoreException("More than single record of '" . $className . "' entity selected");
678
        }
679
680
        return null;
681
    }
682
683
    /**
684
     * Selects DBObject from database.
685
     *
686
     * @param string $query SQL query.
687
     * @param string $types Types string (ex: "isdb").
688
     * @param array $params Parameters in the same order like types string.
689
     * @param mixed $instance Instance of the some DBObject class or it's class name.
690
     *
691
     * @return DBObject Selected DBObject or NULL otherwise.
692
     */
693 View Code Duplication
    public static function selectDBObject($query, $types, $params, $instance) {
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...
694
        $stmt = self::doSelectQuery($query, $types, $params);
695
        $obj = null;
696
        if ($stmt) {
697
            $obj = self::selectDBObjectFromStatement($stmt, $instance);
698
699
            $stmt->close();
700
        }
701
702
        return $obj;
703
    }
704
705
    /**
706
     * Returns list of DB objects by database query statement.
707
     *
708
     * @param resource $stmt Database query statement.
709
     * @param mixed $className Instance of the some DBObject class or it's class name.
710
     *
711
     * @return array<DBObject>
712
     */
713
    public static function selectDBObjectsFromStatement($stmt, $className) {
714
        if (is_object($className)) {
715
            $className = get_class($className);
716
        }
717
718
        if ($stmt->num_rows > 0) {
719
            $objectsList = [];
720
            while ($resultSet = self::bindResults($stmt)) {
721
                $dbObject = new $className();
722
                self::selectDBObjectFromResultSet($dbObject, $resultSet);
723
724
                $recordId = $dbObject->getId();
725
                if (!is_null($recordId)) {
726
                    $objectsList[$recordId] = $dbObject;
727
                } else {
728
                    $objectsList[] = $dbObject;
729
                }
730
            }
731
732
            return $objectsList;
733
        }
734
735
        return [];
736
    }
737
738
    /**
739
     * Selects DBObject list from database.
740
     *
741
     * @param string $query SQL query.
742
     * @param string $types Types string (ex: "isdb").
743
     * @param array $params Parameters in the same order like types string.
744
     * @param mixed $instance Instance of the some DBObject class or it's class name.
745
     *
746
     * @return DBObject Selected DBObject or NULL otherwise.
747
     */
748 View Code Duplication
    public static function selectDBObjects($query, $types, $params, $instance) {
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...
749
        $stmt = self::doSelectQuery($query, $types, $params);
750
        $obj = null;
751
        if ($stmt) {
752
            $obj = self::selectDBObjectsFromStatement($stmt, $instance);
753
754
            $stmt->close();
755
        }
756
757
        return $obj;
758
    }
759
760
    /**
761
     * Executes SQL query with single record and return this record.
762
     *
763
     * @param mixed $query SQL query template string or DBPreparedQuery object
764
     *           if single parameter.
765
     * @param string $types Types string (ex: "isdb").
766
     * @param array $params Parameters in the same order like types string.
767
     *
768
     * @return array Selected record with table names as keys or NULL if no
769
     *           data selected.
770
     * @throws DBCoreException If no one or more than one records selected.
771
     */
772
    public static function selectSingleRecord($query, $types = "", $params = []) {
773 View Code Duplication
        if (!Tools::isInstanceOf($query, new DBPreparedQuery())) {
0 ignored issues
show
Documentation introduced by
new \Asymptix\db\DBPreparedQuery() is of type object<Asymptix\db\DBPreparedQuery>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
774
            $dbQuery = new DBPreparedQuery($query, $types, $params);
775
        } else {
776
            $dbQuery = $query;
777
        }
778
        $stmt = $dbQuery->go();
779
780
        if ($stmt !== false) {
781
            $record = null;
782
            if ($stmt->num_rows === 1) {
783
                $record = self::bindResults($stmt);
784
            }
785
            $stmt->close();
786
787
            if (is_null($record)) {
788
                throw new DBCoreException("No one or more than one records selected.");
789
            }
790
791
            return $record;
792
        }
793
794
        return null;
795
    }
796
797
    /**
798
     * Executes SQL query with single record and value result and return this value.
799
     *
800
     * @param mixed $query SQL query template string or DBPreparedQuery object
801
     *           if single parameter.
802
     * @param string $types Types string (ex: "isdb").
803
     * @param array $params Parameters in the same order like types string.
804
     *
805
     * @return mixed
806
     * @throws DBCoreException If no one or more than one records selected.
807
     */
808
    public static function selectSingleValue($query, $types = "", $params = []) {
809 View Code Duplication
        if (!Tools::isInstanceOf($query, new DBPreparedQuery())) {
0 ignored issues
show
Documentation introduced by
new \Asymptix\db\DBPreparedQuery() is of type object<Asymptix\db\DBPreparedQuery>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
810
            $dbQuery = new DBPreparedQuery($query, $types, $params);
811
        } else {
812
            $dbQuery = $query;
813
        }
814
        $stmt = $dbQuery->go();
815
816
        if ($stmt !== false) {
817
            $value = null;
818
            $numRows = $stmt->num_rows;
819
            if ($numRows === 1) {
820
                $stmt->bind_result($value);
821
                $stmt->fetch();
822
            }
823
            $stmt->close();
824
825
            if ($numRows !== 1) {
826
                throw new DBCoreException("No one or more than one records selected.");
827
            }
828
829
            return $value;
830
        }
831
832
        return null;
833
    }
834
835
}
836
837
/**
838
 * Service exception class.
839
 */
840
class DBCoreException extends \Exception {}
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
841