Completed
Push — master ( ca5c4a...e71512 )
by Dmytro
03:37
created

DBCore::setCurrentConnection()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 6
rs 9.4285
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
     * @param array $params Parameters.
0 ignored issues
show
Bug introduced by
There is no parameter named $params. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
273
     */
274
    public static function bindResults($stmt) {
275
        $resultSet = [];
276
        $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...
277
        $fieldsCounter = 0;
278
        while ($field = $metaData->fetch_field()) {
279
            if (!isset($resultSet[$field->table])) {
280
                $resultSet[$field->table] = [];
281
            }
282
            $resultSet[$field->table][$field->name] = $fieldsCounter++;
283
            $parameterName = "variable" . $fieldsCounter; //$field->name;
284
            $$parameterName = null;
285
            $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...
286
        }
287
        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...
288
        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...
289
            foreach ($resultSet as &$tableResult) {
290
                foreach ($tableResult as &$fieldValue) {
291
                    $fieldValue = $parameters[$fieldValue];
292
                }
293
            }
294
295
            return $resultSet;
296
        }
297
        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...
298
299
        return null;
300
    }
301
302
    /**
303
     * Execute DB SQL queries using Prepared Statements.
304
     *
305
     * @param mixed $query SQL query template string or DBPreparedQuery object
306
     *           if single parameter.
307
     * @param string $types Types string (ex: "isdb").
308
     * @param array $params Parameters in the same order like types string.
309
     *
310
     * @return mixed Statement object or FALSE if an error occurred.
311
     */
312
    private static function doQuery($query, $types = "", $params = []) {
313
        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...
314
            $dbQuery = new DBPreparedQuery($query, $types, $params);
315
        } else {
316
            $dbQuery = $query;
317
        }
318
319
        $stmt = self::connection()->prepare($dbQuery->query);
320
        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...
321
322
        if ($dbQuery->isBindable()) {
323
            if ($dbQuery->isValid()) {
324
                self::bindParameters($stmt, $dbQuery->types, $dbQuery->params);
325
            } else {
326
                throw new DBCoreException(
327
                    "Number of types is not equal parameters number or types string is invalid"
328
                );
329
            }
330
        }
331
332
        $stmt->execute();
333
        self::checkDbError($stmt);
334
335
        return $stmt;
336
    }
337
338
    /**
339
     * Execute DB SQL queries using Prepared Statements.
340
     *
341
     * @param mixed $query SQL query template string or DBPreparedQuery object
342
     *           if single parameter.
343
     * @param string $types Types string (ex: "isdb").
344
     * @param array $params Parameters in the same order like types string.
345
     *
346
     * @return mixed Statement object or FALSE if an error occurred.
347
     */
348
    public static function query($query, $types = "", $params = []) {
349
        return (new DBPreparedQuery($query, $types, $params))->go();
350
    }
351
352
353
    /**
354
     * Execute update DB SQL queries using Prepared Statements.
355
     *
356
     * @param string $query SQL query template string or DBPreparedQuery object
357
     *           if single parameter.
358
     * @param string $types Types string.
359
     * @param array $params Parameters.
360
     *
361
     * @return int Returns the number of affected rows on success and
362
     *           -1 if the last query failed.
363
     */
364
    public static function doUpdateQuery($query, $types = "", $params = []) {
365
        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...
366
            $dbQuery = new DBPreparedQuery($query, $types, $params);
367
        } else {
368
            $dbQuery = $query;
369
        }
370
        $stmt = self::doQuery($dbQuery);
371
372
        switch ($dbQuery->getType()) {
373
            case (DBQueryType::INSERT):
374
                $result = self::connection()->insert_id;
375
                break;
376
            case (DBQueryType::UPDATE):
377
                $result = self::connection()->affected_rows;
378
                break;
379
            default:
380
                $result = self::connection()->affected_rows;
381
        }
382
        $stmt->close();
383
384
        return $result;
385
    }
386
387
    /**
388
     * Execute select DB SQL queries using Prepared Statements.
389
     *
390
     * @param mixed $query SQL query template string or DBPreparedQuery object
391
     *           if single parameter.
392
     * @param string $types Types string (ex: "isdb").
393
     * @param array $params Parameters in the same order like types string.
394
     *
395
     * @return mixed Statement object or FALSE if an error occurred.
396
     */
397
    public static function doSelectQuery($query, $types = "", $params = []) {
398
        $stmt = self::doQuery($query, $types, $params);
399
400
        $stmt->store_result();
401
        self::checkDbError($stmt);
402
403
        return $stmt;
404
    }
405
406
    /**
407
     * Returns list of database table fields.
408
     *
409
     * @param string $tableName Name of the table.
410
     * @return array<string> List of the database table fields (syntax: array[fieldName] = fieldType)
411
     */
412
    public static function getTableFieldsList($tableName) {
413
        if (!empty($tableName)) {
414
            $query = "SHOW FULL COLUMNS FROM " . $tableName;
415
            $stmt = self::doSelectQuery($query);
416
            if ($stmt !== false) {
417
                $stmt->bind_result(
418
                    $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...
419
                );
420
421
                $fieldsList = [];
422
                while ($stmt->fetch()) {
423
                    $fieldsList[$field] = [
424
                        'type' => $type,
425
                        'collation' => $collation,
426
                        'null' => $null,
427
                        'key' => $key,
428
                        'default' => $default,
429
                        'extra' => $extra,
430
                        'privileges' => $privileges,
431
                        'comment' => $comment
432
                    ];
433
                }
434
                $stmt->close();
435
436
                return $fieldsList;
437
            }
438
        }
439
440
        return [];
441
    }
442
443
    /**
444
     * Returns printable SQL field value for table fields list generator.
445
     *
446
     * @param string $type SQL type of the field.
447
     * @param mixed $value Field value.
448
     *
449
     * @return string
450
     */
451
    private static function getPrintableSQLValue($type, $value) {
452
        if (strpos($type, "varchar") === 0
453
         || strpos($type, "text") === 0
454
         || strpos($type, "longtext") === 0
455
         || strpos($type, "enum") === 0
456
         || strpos($type, "char") === 0
457
         || strpos($type, "datetime") === 0
458
         || strpos($type, "timestamp") === 0
459
         || strpos($type, "date") === 0) {
460
            return ('"' . $value . '"');
461
        } elseif (strpos($type, "int") === 0
462
         || strpos($type, "tinyint") === 0
463
         || strpos($type, "smallint") === 0
464
         || strpos($type, "mediumint") === 0
465
         || strpos($type, "bigint") === 0) {
466
            if (!empty($value)) {
467
                return $value;
468
            }
469
470
            return "0";
471
        } elseif (strpos($type, "float") === 0
472
         || strpos($type, "double") === 0
473
         || strpos($type, "decimal") === 0) {
474
            if (!empty($value)) {
475
                return $value;
476
            }
477
478
            return "0.0";
479
        }
480
481
        return $value;
482
    }
483
484
    /**
485
     * Returns printable field description string for table fields list generator.
486
     *
487
     * @param string $field Field name.
488
     * @param array $attributes List of field attributes.
489
     *
490
     * @return string
491
     */
492
    public static function getPrintableFieldString($field, $attributes) {
493
        $extra = trim($attributes['extra']);
494
        $comment = trim($attributes['comment']);
495
496
        $fieldStr = "'" . $field . "' => ";
497
        if ($attributes['null'] === 'YES' && is_null($attributes['default'])) {
498
            $fieldStr.= "null";
499
        } else {
500
            $fieldStr.= self::getPrintableSQLValue($attributes['type'], $attributes['default']);
501
        }
502
        $fieldStr.= ", // " . $attributes['type'] .
503
            ", " . (($attributes['null'] == "NO") ? "not null" : "null")
504
            . ", default '" . $attributes['default'] . "'" .
505
            ($extra ? ", " . $extra : "") .
506
            ($comment ? " (" . $comment . ")" : "") . "\n";
507
508
        return $fieldStr;
509
    }
510
511
    /**
512
     * Outputs comfortable for Bean Class creation list of table fields.
513
     *
514
     * @param string $tableName Name of the Db table.
515
     */
516
    public static function displayTableFieldsList($tableName) {
517
        print("<pre>");
518
        if (!empty($tableName)) {
519
            $fieldsList = self::getTableFieldsList($tableName);
520
            if (!empty($fieldsList)) {
521
                foreach ($fieldsList as $field => $attributes) {
522
                    print(self::getPrintableFieldString($field, $attributes));
523
                }
524
            }
525
        }
526
        print("</pre>");
527
    }
528
529
    /**
530
     * Returns list of fields values with default indexes.
531
     *
532
     * @param array<mixed> $fieldsList List of the table fields (syntax: array[fieldName] = fieldValue)
533
     * @param string $idFieldName Name of the primary key field.
534
     * @return array<mixed>
535
     */
536
    private static function createValuesList($fieldsList, $idFieldName = "") {
537
        $valuesList = [];
538
        foreach ($fieldsList as $fieldName => $fieldValue) {
539
            if ($fieldName != $idFieldName) {
540
                $valuesList[] = $fieldValue;
541
            }
542
        }
543
544
        return $valuesList;
545
    }
546
547
    /**
548
     * Executes SQL INSERT query to the database.
549
     *
550
     * @param DBObject $dbObject DBObject to insert.
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, $debug = false) {
556
        $fieldsList = $dbObject->getFieldsList();
557
        $idFieldName = $dbObject->getIdFieldName();
558
559
        if (Tools::isInteger($fieldsList[$idFieldName])) {
560
            $query = "INSERT 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 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
            if (Tools::isInteger($dbObject->getId())) {
627
                $typesString = "i";
628
            } else {
629
                $typesString = "s";
630
            }
631
            self::doUpdateQuery($query, $typesString, [$dbObject->getId()]);
632
633
            return (self::connection()->affected_rows);
634
        } else {
635
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Asymptix\db\DBCore::deleteDBObject of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
636
        }
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 View Code Duplication
    public static function selectSingleRecord($query, $types = "", $params = []) {
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...
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 View Code Duplication
    public static function selectSingleValue($query, $types = "", $params = []) {
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...
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
            if ($stmt->num_rows === 1) {
819
                $stmt->bind_result($value);
820
                $stmt->fetch();
821
            }
822
            $stmt->close();
823
824
            if (is_null($value)) {
825
                throw new DBCoreException("No one or more than one records selected.");
826
            }
827
828
            return $value;
829
        }
830
831
        return null;
832
    }
833
834
    /**
835
     * Calls DBCore magic methods like:
836
     *    get[User]By[Id]($userId)
837
     *    get[User]By[Email]($email)
838
     *    get[Users]()
839
     *    delete[Users]($ids)
840
     *    delete[User]($userId)
841
     *    set[User]Activation($activationFieldName, $flagValue).
842
     *
843
     * @param string $methodName Name of the magic method.
844
     * @param array $methodParams List of dynamic parameters.
845
     *
846
     * @return mixed
847
     * @throws DBCoreException
848
     */
849
    public static function __callStatic($methodName, $methodParams) {
850
        if (strrpos($methodName, "ies") == strlen($methodName) - 3) {
851
            $methodName = substr($methodName, 0, strlen($methodName) - 3) . "ys";
852
        }
853
854
        /*
855
         * Get database record object by Id
856
         */
857
        if (preg_match("#get([a-zA-Z]+)ById#", $methodName, $matches)) {
858
            $dbSelector = new DBSelector($matches[1]);
859
860
            return $dbSelector->selectDBObjectById($methodParams[0]);
861
        }
862
863
        /*
864
         * Get database record object by some field value
865
         */
866
        if (preg_match("#get([a-zA-Z]+)By([a-zA-Z]+)#", $methodName, $matches)) {
867
            if (empty($methodParams[0])) {
868
                return null;
869
            }
870
            $dbSelector = new DBSelector($matches[1]);
871
872
            $fieldName = substr(strtolower(preg_replace("#([A-Z]{1})#", "_$1", $matches[2])), 1);
873
874
            return $dbSelector->selectDBObjectByField($fieldName, $methodParams[0]);
875
        }
876
877
        /*
878
         * Get all database records
879
         */
880
        if (preg_match("#get([a-zA-Z]+)s#", $methodName, $matches)) {
881
            return self::Selector()->selectDBObjects();
882
        }
883
884
        /*
885
         * Delete selected records from the database
886
         */
887
        if (preg_match("#delete([a-zA-Z]+)s#", $methodName, $matches)) {
888
            $className = $matches[1];
889
            $idsList = $methodParams[0];
890
891
            $idsList = array_filter($idsList, "isInteger");
892
            if (!empty($idsList)) {
893
                $itemsNumber = count($idsList);
894
                $types = DBPreparedQuery::sqlSingleTypeString("i", $itemsNumber);
895
                $dbObject = new $className();
896
897
                if (!isInstanceOf($dbObject, $className)) {
898
                    throw new DBCoreException("Class with name '" . $className . "' is not exists");
899
                }
900
901
                $query = "DELETE FROM " . $dbObject->getTableName() . "
902
                          WHERE " . $dbObject->getIdFieldName() . "
903
                             IN (" . DBPreparedQuery::sqlQMString($itemsNumber) . ")";
904
905
                return self::doUpdateQuery($query, $types, $idsList);
906
            }
907
908
            return 0;
909
        }
910
911
        /*
912
         * Delete selected record from the database
913
         */
914
        if (preg_match("#delete([a-zA-Z]+)#", $methodName, $matches)) {
915
            return call_user_func(
916
                [self::getInstance(), $methodName . "s"],
917
                [$methodParams[0]]
918
            );
919
        }
920
921
        /*
922
         * Set activation value of selected records
923
         */
924
        if (preg_match("#set([a-zA-Z]+)Activation#", $methodName, $matches)) {
925
            $className = $matches[1];
926
            if (strrpos($className, "ies") == strlen($className) - 3) {
927
                $className = substr($className, 0, strlen($className) - 3) . "y";
928
            } else {
929
                $className = substr($className, 0, strlen($className) - 1);
930
            }
931
932
            $idsList = $methodParams[0];
933
            $activationFieldName = $methodParams[1];
934
            $activationValue = $methodParams[2];
935
936
            if (empty($activationFieldName)) {
937
                throw new DBCoreException("Invalid activation field name");
938
            }
939
940
            $idsList = array_filter($idsList, "isInteger");
941
            if (!empty($idsList)) {
942
                $itemsNumber = count($idsList);
943
                $types = DBPreparedQuery::sqlSingleTypeString("i", $itemsNumber);
944
                $dbObject = new $className();
945
946
                if (!isInstanceOf($dbObject, $className)) {
947
                    throw new DBCoreException("Class with name '" . $className . "' is not exists");
948
                }
949
950
                $query = "UPDATE " . $dbObject->getTableName() . " SET `" . $activationFieldName . "` = '" . $activationValue . "'
951
                          WHERE " . $dbObject->getIdFieldName() . " IN (" . DBPreparedQuery::sqlQMString($itemsNumber) . ")";
952
953
                return self::doUpdateQuery($query, $types, $idsList);
954
            }
955
        }
956
957
        throw new DBCoreException('No such method "' . $methodName . '"');
958
    }
959
960
}
961
962
/**
963
 * Service exception class.
964
 */
965
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...
966