Completed
Push — master ( 417a50...a61c99 )
by Dmytro
07:12
created

DBCore::__callStatic()   D

Complexity

Conditions 15
Paths 34

Size

Total Lines 110
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 15
eloc 55
c 3
b 1
f 0
nc 34
nop 2
dl 0
loc 110
rs 4.9121

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
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 - 2016, 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
        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...
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
    /**
353
     * Execute update DB SQL queries using Prepared Statements.
354
     *
355
     * @param string $query SQL query template string or DBPreparedQuery object
356
     *           if single parameter.
357
     * @param string $types Types string.
358
     * @param array $params Parameters.
359
     *
360
     * @return int Returns the number of affected rows on success and
361
     *           -1 if the last query failed.
362
     */
363
    public static function doUpdateQuery($query, $types = "", $params = []) {
364
        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...
365
            $dbQuery = new DBPreparedQuery($query, $types, $params);
366
        } else {
367
            $dbQuery = $query;
368
        }
369
        $stmt = self::doQuery($dbQuery);
370
371
        switch ($dbQuery->getType()) {
372
            case (DBQueryType::INSERT):
373
                $result = self::connection()->insert_id;
374
                break;
375
            case (DBQueryType::UPDATE):
376
                $result = self::connection()->affected_rows;
377
                break;
378
            default:
379
                $result = self::connection()->affected_rows;
380
        }
381
        $stmt->close();
382
383
        return $result;
384
    }
385
386
    /**
387
     * Execute select DB SQL queries using Prepared Statements.
388
     *
389
     * @param mixed $query SQL query template string or DBPreparedQuery object
390
     *           if single parameter.
391
     * @param string $types Types string (ex: "isdb").
392
     * @param array $params Parameters in the same order like types string.
393
     *
394
     * @return mixed Statement object or FALSE if an error occurred.
395
     */
396
    public static function doSelectQuery($query, $types = "", $params = []) {
397
        $stmt = self::doQuery($query, $types, $params);
398
399
        $stmt->store_result();
400
        self::checkDbError($stmt);
401
402
        return $stmt;
403
    }
404
405
    /**
406
     * Returns list of database table fields.
407
     *
408
     * @param string $tableName Name of the table.
409
     * @return array<string> List of the database table fields (syntax: array[fieldName] = fieldType)
410
     */
411
    public static function getTableFieldsList($tableName) {
412
        if (!empty($tableName)) {
413
            $query = "SHOW FULL COLUMNS FROM " . $tableName;
414
            $stmt = self::doSelectQuery($query);
415
            if ($stmt !== false) {
416
                $stmt->bind_result(
417
                    $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...
418
                );
419
420
                $fieldsList = [];
421
                while ($stmt->fetch()) {
422
                    $fieldsList[$field] = [
423
                        'type' => $type,
424
                        'collation' => $collation,
425
                        'null' => $null,
426
                        'key' => $key,
427
                        'default' => $default,
428
                        'extra' => $extra,
429
                        'privileges' => $privileges,
430
                        'comment' => $comment
431
                    ];
432
                }
433
                $stmt->close();
434
435
                return $fieldsList;
436
            }
437
        }
438
439
        return [];
440
    }
441
442
    /**
443
     * Returns printable SQL field value for table fields list generator.
444
     *
445
     * @param string $type SQL type of the field.
446
     * @param mixed $value Field value.
447
     *
448
     * @return string
449
     */
450
    private static function getPrintableSQLValue($type, $value) {
451
        if (strpos($type, "varchar") === 0
452
         || strpos($type, "text") === 0
453
         || strpos($type, "longtext") === 0
454
         || strpos($type, "enum") === 0
455
         || strpos($type, "char") === 0
456
         || strpos($type, "datetime") === 0
457
         || strpos($type, "timestamp") === 0
458
         || strpos($type, "date") === 0) {
459
            return ('"' . $value . '"');
460
        } elseif (strpos($type, "int") === 0
461
         || strpos($type, "tinyint") === 0
462
         || strpos($type, "smallint") === 0
463
         || strpos($type, "mediumint") === 0
464
         || strpos($type, "bigint") === 0) {
465
            if (!empty($value)) {
466
                return $value;
467
            }
468
469
            return "0";
470
        } elseif (strpos($type, "float") === 0
471
         || strpos($type, "double") === 0
472
         || strpos($type, "decimal") === 0) {
473
            if (!empty($value)) {
474
                return $value;
475
            }
476
477
            return "0.0";
478
        }
479
480
        return $value;
481
    }
482
483
    /**
484
     * Returns printable field description string for table fields list generator.
485
     *
486
     * @param string $field Field name.
487
     * @param array $attributes List of field attributes.
488
     *
489
     * @return string
490
     */
491
    public static function getPrintableFieldString($field, $attributes) {
492
        $extra = trim($attributes['extra']);
493
        $comment = trim($attributes['comment']);
494
495
        $fieldStr = "'" . $field . "' => ";
496
        if ($attributes['null'] === 'YES' && is_null($attributes['default'])) {
497
            $fieldStr.= "null";
498
        } else {
499
            $fieldStr.= self::getPrintableSQLValue($attributes['type'], $attributes['default']);
500
        }
501
        $fieldStr.= ", // " . $attributes['type'] .
502
            ", " . (($attributes['null'] == "NO") ? "not null" : "null")
503
            . ", default '" . $attributes['default'] . "'" .
504
            ($extra ? ", " . $extra : "") .
505
            ($comment ? " (" . $comment . ")" : "") . "\n";
506
507
        return $fieldStr;
508
    }
509
510
    /**
511
     * Outputs comfortable for Bean Class creation list of table fields.
512
     *
513
     * @param string $tableName Name of the Db table.
514
     */
515
    public static function displayTableFieldsList($tableName) {
516
        print("<pre>");
517
        if (!empty($tableName)) {
518
            $fieldsList = self::getTableFieldsList($tableName);
519
            if (!empty($fieldsList)) {
520
                foreach ($fieldsList as $field => $attributes) {
521
                    print(self::getPrintableFieldString($field, $attributes));
522
                }
523
            }
524
        }
525
        print("</pre>");
526
    }
527
528
    /**
529
     * Returns list of fields values with default indexes.
530
     *
531
     * @param array<mixed> $fieldsList List of the table fields (syntax: array[fieldName] = fieldValue)
532
     * @param string $idFieldName Name of the primary key field.
533
     * @return array<mixed>
534
     */
535
    private static function createValuesList($fieldsList, $idFieldName = "") {
536
        $valuesList = [];
537
        foreach ($fieldsList as $fieldName => $fieldValue) {
538
            if ($fieldName != $idFieldName) {
539
                $valuesList[] = $fieldValue;
540
            }
541
        }
542
543
        return $valuesList;
544
    }
545
546
    /**
547
     * Executes SQL INSERT query to the database.
548
     *
549
     * @param DBObject $dbObject DBObject to insert.
550
     * @param bool $ignore Ignore unique indexes or not.
551
     * @param bool $debug Debug mode flag.
552
     *
553
     * @return int Insertion ID (primary key value) or null on debug.
554
     */
555
    public static function insertDBObject($dbObject, $ignore = false, $debug = false) {
556
        $fieldsList = $dbObject->getFieldsList();
557
        $idFieldName = $dbObject->getIdFieldName();
558
559
        if (Tools::isInteger($fieldsList[$idFieldName])) {
560
            $query = "INSERT " . ($ignore ? 'IGNORE' : 'INTO') . " " . $dbObject->getTableName() . "
561
                          SET " . DBPreparedQuery::sqlQMValuesString($fieldsList, $idFieldName);
562
            $typesString = DBPreparedQuery::sqlTypesString($fieldsList, $idFieldName);
563
            $valuesList = self::createValuesList($fieldsList, $idFieldName);
564
        } else {
565
            $query = "INSERT " . ($ignore ? 'IGNORE' : 'INTO') . " " . $dbObject->getTableName() . "
566
                          SET " . DBPreparedQuery::sqlQMValuesString($fieldsList);
567
            $typesString = DBPreparedQuery::sqlTypesString($fieldsList);
568
            $valuesList = self::createValuesList($fieldsList);
569
        }
570
571
        if ($debug) {
572
            DBQuery::showQueryDebugInfo($query, $typesString, $valuesList);
573
574
            return null;
575
        }
576
        self::doUpdateQuery($query, $typesString, $valuesList);
577
578
        return (self::connection()->insert_id);
579
    }
580
581
    /**
582
     * Executes SQL UPDATE query to the database.
583
     *
584
     * @param DBObject $dbObject DBObject to update.
585
     * @param bool $debug Debug mode flag.
586
     *
587
     * @return int Returns the number of affected rows on success, and -1 if
588
     *           the last query failed.
589
     */
590
    public static function updateDBObject($dbObject, $debug = false) {
591
        $fieldsList = $dbObject->getFieldsList();
592
        $idFieldName = $dbObject->getIdFieldName();
593
594
        $query = "UPDATE " . $dbObject->getTableName() . "
595
                  SET " . DBPreparedQuery::sqlQMValuesString($fieldsList, $idFieldName) . "
596
                  WHERE " . $idFieldName . " = ?
597
                  LIMIT 1";
598
        $typesString = DBPreparedQuery::sqlTypesString($fieldsList, $idFieldName);
599
        if (Tools::isInteger($fieldsList[$idFieldName])) {
600
            $typesString.= "i";
601
        } else {
602
            $typesString.= "s";
603
        }
604
        $valuesList = self::createValuesList($fieldsList, $idFieldName);
605
        $valuesList[] = $dbObject->getId();
606
607
        if ($debug) {
608
            DBQuery::showQueryDebugInfo($query, $typesString, $valuesList);
609
        } else {
610
            return self::doUpdateQuery($query, $typesString, $valuesList);
611
        }
612
    }
613
614
    /**
615
     * Executes SQL DELETE query to the database.
616
     *
617
     * @param DBObject $dbObject DBObject to delete.
618
     *
619
     * @return int Returns the number of affected rows on success, and -1 if
620
     *           the last query failed.
621
     */
622
    public static function deleteDBObject($dbObject) {
623
        if (!empty($dbObject) && is_object($dbObject)) {
624
            $query = "DELETE FROM " . $dbObject->getTableName() .
625
                     " WHERE " . $dbObject->getIdFieldName() . " = ? LIMIT 1";
626
627
            $typesString = "s";
628
            if (Tools::isInteger($dbObject->getId())) {
629
                $typesString = "i";
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
        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...
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
        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...
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