Failed Conditions
Push — master ( 3bfd7d...be4c95 )
by Ryan
03:59 queued 02:37
created

Database::createTable()   C

Complexity

Conditions 17
Paths 14

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 29.1586

Importance

Changes 6
Bugs 3 Features 0
Metric Value
cc 17
eloc 20
c 6
b 3
f 0
nc 14
nop 3
dl 0
loc 32
ccs 15
cts 23
cp 0.6522
crap 29.1586
rs 5.2166

How to fix   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
namespace Godsgood33\Php_Db;
3
4
use mysqli;
5
use stdClass;
6
use Exception;
7
use InvalidArgumentException;
8
9
use Monolog\Logger;
10
use Monolog\Formatter\LineFormatter;
11
use Monolog\Handler\StreamHandler;
12
13
use Godsgood33\Php_Db\Exceptions\MissingOrInvalidParam;
14
use Godsgood33\Php_Db\Exceptions\ConnectError;
15
use Godsgood33\Php_Db\Exceptions\MissingInterfaceAndMethods;
16
use Godsgood33\Php_Db\Exceptions\QueryError;
17
18
/**
19
 * Constant to define that we want to return an object
20
 *
21
 * @var int
22
 */
23
define('MYSQLI_OBJECT', 4);
24
25
/**
26
 * Constant to return consistent date format
27
 *
28
 * @var string
29
 */
30
define('MYSQL_DATE', 'Y-m-d');
31
32
/**
33
 * Constant to return consistent datetime format
34
 *
35
 * @var string
36
 */
37
define('MYSQL_DATETIME', 'Y-m-d H:i:s');
38
39
/**
40
 * A generic database class
41
 *
42
 * @author Ryan Prather <[email protected]>
43
 */
44
class Database
45
{
46
47
    /**
48
     * The mysqli connection
49
     *
50
     * @access protected
51
     * @var mysqli
52
     */
53
    protected $_c;
54
55
    /**
56
     * To store the SQL statement
57
     *
58
     * @access private
59
     * @var string
60
     */
61
    private $_sql = '';
62
63
    /**
64
     * A variable to store the type of query that is being run
65
     *
66
     * @access private
67
     * @var int
68
     */
69
    private $_queryType = null;
70
71
    /**
72
     * The result of the query
73
     *
74
     * @access protected
75
     * @var mixed
76
     */
77
    protected $_result = null;
78
79
    /**
80
     * Log level
81
     *
82
     * @access private
83
     * @var int
84
     */
85
    private $_logLevel = Logger::ERROR;
86
87
    /**
88
     * Variable to store the logger
89
     *
90
     * @access private
91
     * @var \Monolog\Logger
92
     */
93
    private $_logger = null;
94
95
    /**
96
     * Path for the logger to log the file
97
     *
98
     * @access private
99
     * @var string
100
     */
101
    private $_logPath = null;
102
103
    /**
104
     * Variable to store the most recent insert ID from an insert query
105
     *
106
     * @access protected
107
     * @var mixed
108
     */
109
    protected $_insertId = null;
110
111
    /**
112
     * Constructor
113
     *
114
     * @param string $strLogPath
115
     *            [optional] absolute log path for the log file
116
     * @param mysqli $dbh
117
     *            [optional]
118
     *            [by ref]
119
     *            mysqli object to perform queries.
120
     * @param int $intLogLevel
121
     *            [optional] Level of the log file to output
122
     *
123
     * @uses PHP_DB_ENCRYPT bool to define if the password is encrypted
124
     * @uses PHP_DB_PWD string to store the password
125
     * @uses PHP_DB_SERVER string to store the database server name or IP
126
     * @uses PHP_DB_USER string to store the name of the user used to connect to the server
127
     * @uses PHP_DB_SCHEMA string to store the default schema to connect to
128
     * @uses PHP_DB_LOG_LEVEL int to store the PSR-4 log level for the library
129
     * @uses PHP_DB_CLI_LOG bool to store if logs should be echoed to STDOUT
130
     */
131
    public function __construct($strLogPath = __DIR__, mysqli &$dbh = null, $intLogLevel = null)
132
    {
133
        $this->checkLog($strLogPath);
134
        $this->checkLogLevel($intLogLevel);
135
        $this->initLog();
136
        $this->checkRequiredParams();
137
        $this->init($dbh);
138
139
        // update the logger with the connection details
140
        $this->_logger->info("Database connected");
141
        $this->_logger->debug("Connection details:", [
142
            'Server' => PHP_DB_SERVER,
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SERVER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
143
            'User'   => PHP_DB_USER,
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_USER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
144
            'Schema' => PHP_DB_SCHEMA
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SCHEMA was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
145
        ]);
146
147
        $this->setVar("time_zone", "+00:00");
148
        $this->setVar("sql_mode", "");
149
    }
150
151
    /**
152
     * Function to make sure that the database is connected
153
     *
154
     * @return bool
155
     *
156
     * @throws ConnectError
157
     */
158
    public function isConnected(): bool
159
    {
160
        $this->_logger->info("Checking for live connection");
161
162
        if (is_a($this->_c, 'mysqli')) {
163
            $ret = $this->_c->ping();
164
        } else {
165
            throw new ConnectError("Connection lost");
166
        }
167
168
        return $ret;
169
    }
170
171
    /**
172
     * Setter function for _logger
173
     *
174
     * @param Logger $log
175
     *
176
     * @return bool
177
     */
178
    public function setLogger(Logger $log): bool
179
    {
180
        // set the logger
181
        $this->_logger->debug("Setting logger");
182
        $this->_logger = $log;
183
        return true;
184
    }
185
186
    /**
187
     * Getter function for _logLevel
188
     *
189
     * @return int
190
     */
191
    public function getLogLevel(): int
192
    {
193
        $level = $this->_logLevel;
194
195
        $this->_logger->debug("Getting log level ({$level})");
196
        return $level;
197
    }
198
199
    /**
200
     * Getter function for _queryType
201
     *
202
     * @return int
203
     */
204
    public function getQueryType(): int
205
    {
206
        $this->_logger->debug("Getting query type");
207
        return $this->_queryType;
208
    }
209
210
    /**
211
     * Setter function for _queryType
212
     *
213
     * @param int $qt
214
     *      Class constants that store query type
215
     *
216
     * @return Database
217
     */
218
    private function setQueryType(int $qt): Database
219
    {
220
        $this->_logger->debug("Setting query type");
221
        $this->_queryType = $qt;
222
223
        return $this;
224
    }
225
226
    /**
227
     * Magic method to convert the class to a string represented by the SQL query
228
     *
229
     * @return string|null
230
     */
231
    public function __toString(): string
232
    {
233
        $this->_logger->notice("__toString");
234
        $this->_logger->debug($this->_sql);
235
        return $this->_sql;
236
    }
237
238
    /**
239
     * Function to return the currently selected database schema
240
     *
241
     * @return string|bool
242
     *      Returns the string name of the selected schema or FALSE if none selected
243
     */
244
    public function getSchema()
245
    {
246
        $res = $this->_c->query("SELECT DATABASE()");
247
        $row = $res->fetch_row();
248
249
        $this->_logger->debug("Getting schema {$row[0]}");
250
        return $row[0];
251
    }
252
253
    /**
254
     * Function to set schema
255
     *
256
     * @param string $strSchema
257
     *
258
     * @return bool
259
     *
260
     * @throws QueryError
261
     */
262
    public function setSchema(string $strSchema): bool
263
    {
264
        $this->_logger->info("Setting schema to {$strSchema}");
265
        if (! $this->_c->select_db($strSchema)) {
266
            $this->_logger->error("Unknown schema {$strSchema}");
267
            throw new QueryError("Unknown database schema $strSchema");
268
        }
269
        return true;
270
    }
271
272
    /**
273
     * Method to set a MYSQL variable
274
     *
275
     * @param string $strName
276
     *      Name of the SQL variable to set
277
     * @param string $strVal
278
     *      Value for the variable
279
     *
280
     * @return bool
281
     */
282
    public function setVar(string $strName, ?string $strVal): bool
283 131
    {
284
        if (empty($strName)) {
285
            $this->_logger->debug("name is blank", [
286 131
                'name'  => $strName
287 131
            ]);
288
            return false;
289
        }
290
291
        $this->_logger->debug("Setting {$strName} = '{$strVal}'");
292 131
293
        if ($this->_c->real_query("SET $strName = {$this->_escape($strVal)}")) {
294 131
            $this->_logger->info("Var set");
295
            return true;
296 131
        } else {
297 131
            $this->_logger->error("Failed to set variable {$this->_c->error}");
298
            return false;
299
        }
300
    }
301 131
302 131
    /**
303
     * Function to execute the statement
304
     *
305
     * @param int $return
306 131
     *            [optional]
307
     *            MYSQLI constant to control what is returned from the mysqli_result object
308
     * @param string $strSql
309
     *            [optional]
310
     *            Optional SQL query
311
     *
312
     * @throws QueryError
313 131
     * @throws ConnectError
314 1
     *
315 1
     * @return mixed
316 131
     */
317
    public function execute(int $return = MYSQLI_OBJECT, ?string $strSql = null)
318
    {
319 131
        if (! is_null($strSql)) {
320
            $this->_sql = $strSql;
321
        }
322
323
        $this->_result = false;
324
        $this->_insertId = null;
325 131
326 131
        $this->isConnected();
327 131
328
        $this->_logger->info("Executing {$this->_queryType} query");
329
        $this->_logger->debug($this->_sql);
330
331
        try {
332
            if (in_array($this->_queryType, [
333 131
                DBConst::SELECT,
334
                DBConst::SELECT_COUNT
335
            ])) {
336 131
                $this->_result = $this->_c->query($this->_sql);
337
                if ($this->_c->error) {
338
                    $this->_logger->error("There is an error {$this->_c->error}");
339
                    $this->_logger->debug("Errored on query", [$this->_sql]);
340
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
341
                }
342 131
            } else {
343 131
                $this->_result = $this->_c->real_query($this->_sql);
344 131
                if ($this->_c->errno) {
345 131
                    $this->_logger->error("There was an error {$this->_c->error}");
346 131
                    $this->_logger->debug("Errored on query", [$this->_sql]);
347
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
348
                }
349 131
            }
350 131
351 131
            $this->_logger->debug("Checking for query results");
352
            $this->_result = $this->checkResults($return);
353
        } catch (QueryError $e) {
354
            $this->_logger->error($e);
355
        }
356
357
        return $this->_result;
358
    }
359
360 17
    /**
361
     * Function to check the results and return what is expected
362 17
     *
363
     * @param mixed $returnType
364 17
     *            [optional]
365 16
     *            Optional return mysqli_result return type
366
     *
367 1
     * @return mixed
368
     */
369
    protected function checkResults(int $returnType)
370 16
    {
371
        $res = null;
372
373
        // check the sql results and process appropriately
374
        if (in_array($this->_queryType, [DBConst::CREATE_TABLE, DBConst::ALTER_TABLE, DBConst::TRUNCATE, DBConst::DROP])) {
375
            $res = $this->_result;
376
        } elseif (in_array($this->_queryType, [DBConst::INSERT, DBConst::EXTENDED_INSERT, DBConst::DELETE, DBConst::UPDATE, DBConst::EXTENDED_UPDATE, DBConst::REPLACE, DBConst::EXTENDED_REPLACE, DBConst::DELETE])) {
377
            $res = $this->_c->affected_rows;
378
379
            if (in_array($this->_queryType, [DBConst::INSERT, DBConst::REPLACE, DBConst::EXTENDED_INSERT])) {
380 1
                $this->_insertId = $this->_c->insert_id;
381
            }
382
        } elseif ($this->_queryType == DBConst::SELECT_COUNT) {
383 1
            if (! is_a($this->_result, 'mysqli_result')) {
384 1
                $this->_logger->error("Error with return on query");
385 1
                return null;
386
            }
387
388
            if ($this->_result->num_rows == 1) {
389
                $row = $this->_result->fetch_assoc();
390
                if (isset($row['count'])) {
391
                    $this->_logger->debug("Returning SELECT_COUNT query", [
392
                        'count' => $row['count']
393 1
                    ]);
394
                    $res = $row['count'];
395 1
                }
396
            } elseif ($this->_result->num_rows > 1) {
397 1
                $this->_logger->debug("Returning SELECT_COUNT query", [
398 1
                    'count' => $this->_result->num_rows
399
                ]);
400
                $res = $this->_result->num_rows;
401
            }
402
403
            mysqli_free_result($this->_result);
404
        } else {
405
            $method = "mysqli_fetch_object";
406 1
            if ($returnType == MYSQLI_ASSOC) {
407
                $method = "mysqli_fetch_assoc";
408 1
            } elseif ($returnType == MYSQLI_NUM) {
409 1
                $method = "mysqli_fetch_array";
410
            }
411
412
            if (is_a($this->_result, 'mysqli_result')) {
413
                if ($this->_result->num_rows > 1) {
414
                    $res = [];
415
                    while ($row = call_user_func($method, $this->_result)) {
416
                        $res[] = $row;
417
                    }
418
                } else {
419
                    $res = call_user_func($method, $this->_result);
420 101
                }
421
            } else {
422 101
                $this->_logger->error("Error with return on query");
423 101
                return null;
424
            }
425 101
        }
426
427
        if ($this->_c->error) {
428
            $this->_logger->error("Encountered a SQL error", ['error' => $this->_c->error, 'list' => $this->_c->error_list]);
429
            $this->_logger->debug("Debug", ['debug' => debug_backtrace()]);
430
            return null;
431
        }
432
433 67
        return $res;
434
    }
435 67
436 67
    /**
437 67
     * Function to pass through calling the query function (used for backwards compatibility and for more complex queries that aren't currently supported)
438
     * Nothing is escaped
439
     *
440
     * @param string $strSql
441
     *            [optional]
442
     *            Optional query to pass in and execute
443
     *
444
     * @return \mysqli_result|bool
445
     */
446 1
    public function query(?string $strSql = null)
447
    {
448 1
        return is_null($strSql) ? $this->_c->query($this->_sql) : $this->_c->query($strSql);
449 1
    }
450
451 1
    /**
452 1
     * A function to build a select query
453
     *
454
     * @param string $strTableName
455
     *            The table to query
456
     * @param array|string $fields
457
     *            [optional]
458
     *            Optional array of fields to return (defaults to '*')
459
     * @param array:DBWhere|DBWhere $arrWhere
460
     *            [optional] Where clause data
461
     * @param array $arrFlags
462
     *            [optional]
463
     *            Optional 2-dimensional array to allow other flags
464
     *
465
     * @see Database::flags()
466
     *
467 2
     * @throws Exception
468
     * @throws InvalidArgumentException
469 2
     *
470 2
     * @return mixed
471 1
     *
472 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
473
     */
474 1
    public function select(string $strTableName, $fields = null, $arrWhere = [], ?array $arrFlags = [])
475
    {
476
        $this->_sql = null;
477
        $this->setQueryType(DBConst::SELECT);
478
        $this->checkTableName($strTableName);
479
480
        // starting building the query
481
        $this->_logger->debug("Starting SELECT query of {$strTableName}", [
482
            'fields' => $this->fields($fields)
483
        ]);
484
        $this->_sql = "SELECT " . $this->fields($fields) . " FROM $strTableName";
485
486
        // add in any joins
487 131
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins']) && count($arrFlags['joins'])) {
488
            $this->_logger->debug("Adding joins", [
489 131
                'joins' => implode(' ', $arrFlags['joins'])
490 1
            ]);
491 1
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
492
        } else {
493 1
            $this->_logger->debug("No joins");
494
        }
495
496 131
        // parse the where clauses
497
        $where = $this->parseClause($arrWhere);
498 131
499 131
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_array($where) is always true.
Loading history...
introduced by
The condition is_null($where) is always false.
Loading history...
500 131
            $where_str = " WHERE";
501
            $this->_logger->debug("Parsing where clause and adding to query");
502 1
            foreach ($where as $x => $w) {
503 1
                if ($x > 0) {
504
                    $where_str .= " {$w->sqlOperator}";
505
                }
506
                $where_str .= $w;
507
            }
508
            if (strlen($where_str) > strlen(" WHERE")) {
509
                $this->_sql .= $where_str;
510
            }
511
        }
512
513
        // search for any other flags (order, having, group)
514
        if (is_array($arrFlags) && count($arrFlags)) {
515
            $this->_logger->debug("Parsing flags and adding to query", $arrFlags);
516
            $this->_sql .= $this->flags($arrFlags);
517
        }
518
519
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
520
            return $this->execute();
521
        }
522 15
523
        return $this->_sql;
524 15
    }
525 2
526
    /**
527
     * Function to build a query to check the number of rows in a table
528 15
     *
529 15
     * @param string $strTableName
530
     *            The table to query
531 15
     * @param array:DBWhere|DBWhere $arrWhere
532
     *            [optional]
533 15
     *            Optional 2-dimensional array to build where clause
534 15
     * @param array $arrFlags
535
     *            [optional]
536
     *            Optional 2-dimensional array to add flags
537 15
     *
538 15
     * @see Database::flags()
539 15
     *
540
     * @return string|int|NULL
541 6
     *
542 6
     * @throws InvalidArgumentException
543
     *
544
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
545
     */
546
    public function selectCount(string $strTableName, $arrWhere = [], ?array $arrFlags = [])
547
    {
548 9
        $this->_sql = null;
549 9
        $this->setQueryType(DBConst::SELECT_COUNT);
550 1
        $this->checkTableName($strTableName);
551 1
552 1
        // start building query
553
        $this->_sql = "SELECT COUNT(1) AS 'count' FROM $strTableName";
554
555
        // add in any joins
556 14
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
557 14
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
558 1
        }
559 1
560
        // parse where clauses
561
        $where = $this->parseClause($arrWhere);
562 15
563
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_array($where) is always true.
Loading history...
introduced by
The condition is_null($where) is always false.
Loading history...
564
            $where_str = " WHERE";
565
            $this->_logger->debug("Parsing where clause and adding to query");
566
            foreach ($where as $x => $w) {
567
                if ($x > 0) {
568
                    $where_str .= " {$w->sqlOperator}";
569
                }
570
                $where_str .= $w;
571
            }
572
            if (strlen($where_str) > strlen(" WHERE")) {
573
                $this->_sql .= $where_str;
574 14
            }
575
        }
576 14
577
        // add in additional flags (group, having, order)
578
        if (is_array($arrFlags) && count($arrFlags)) {
579 14
            $this->_sql .= $this->flags($arrFlags);
580 5
        }
581 9
582 2
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
583
            return $this->execute();
584 2
        }
585 2
586
        return $this->_sql;
587 7
    }
588 1
589
    /**
590
     * Function to build an insert query statement
591
     *
592
     * @param string $strTableName
593 1
     * @param mixed $arrParams
594 1
     * @param bool $blnToIgnore
595 1
     *
596 1
     * @return string|NULL
597 1
     *
598
     * @throws InvalidArgumentException
599 1
     * @throws MissingInterfaceAndMethods
600
     *
601
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
602
     */
603
    public function insert(string $strTableName, $params, bool $blnToIgnore = false)
604
    {
605
        $this->_sql = null;
606
        $this->setQueryType(DBConst::INSERT);
607
        $this->checkTableName($strTableName);
608 1
609
        // start building query
610 6
        $this->_sql = "INSERT" . ($blnToIgnore ? " IGNORE" : "") . " INTO {$strTableName}";
611 6
612 1
        $paramType = $this->checkParamType($params);
613 5
614 1
        // add in field parameters and values
615
        if ($paramType == DBConst::ARRAY_PRIMATIVE) {
616
            $keys = array_keys($params);
617 6
            $vals = array_values($params);
618 5
            $this->_sql .= " (`" . implode("`,`", $keys) . "`)";
619 3
            $this->_sql .= " VALUES (" . implode(",", array_map([
620 3
                $this,
621 3
                '_escape'
622
            ], $vals)) . ")";
623
        } elseif ($paramType == DBConst::STRING_SELECT) {
624 2
            $this->_sql .= " {$params}";
625
        } elseif ($paramType == DBConst::OBJECT) {
626
            $arr = $params->insert();
627 1
            $keys = array_keys($arr);
628 1
            $vals = array_values($arr);
629
            $this->_sql .= " (`" . implode("`,`", $keys) . "`) VALUES ";
630
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], $vals)) . ")";
631
        } else {
632 13
            throw new InvalidArgumentException("Invalid param type");
633
        }
634
635
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
636
            return $this->execute();
637
        }
638 13
639
        return $this->_sql;
640
    }
641
642
    /**
643
     * Function to create an extended insert query statement
644
     *
645
     * @param string $strTableName
646
     *            The table name that the data is going to be inserted on
647
     * @param array $arrFields
648
     *            An array of field names that each value represents
649
     * @param mixed $params
650
     *            An array of array of values or a string with a SELECT statement to populate the insert with
651 2
     * @param bool $blnToIgnore
652
     *            [optional]
653 2
     *            Boolean to decide if we need to use the INSERT IGNORE INTO syntax
654
     *
655
     * @return NULL|string Returns the SQL if PHP_DB_AUTORUN is set to false, else it returns the output from running.
656
     *
657
     * @throws InvalidArgumentException
658
     * @throws MissingOrInvalidParam
659
     * @throws MissingInterfaceAndMethods
660
     *
661
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
662
     */
663
    public function extendedInsert(string $strTableName, array $arrFields, $params, bool $blnToIgnore = false)
664
    {
665
        $this->_sql = null;
666
        $this->setQueryType(DBConst::EXTENDED_INSERT);
667
        $this->checkTableName($strTableName);
668
669
        if ($this->checkParamType($arrFields) !== DBConst::ARRAY_PRIMATIVE) {
670
            throw new InvalidArgumentException('Invalid field list type');
671
        }
672
673
        // start building query
674
        $this->_sql = "INSERT ".
675
            ($blnToIgnore ? "IGNORE " : "").
676
            "INTO $strTableName ".
677
            "(`".implode("`,`", $arrFields)."`)";
678
679 37
        $paramType = $this->checkParamType($params);
680
        $this->_sql .= " VALUES ";
681 37
682 37
        if ($paramType == DBConst::COLLECTION || $paramType == DBConst::ARRAY_OBJECT) {
683
            foreach ($params as $p) {
684
                $key_value = $p->insert();
685 37
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
686 37
            }
687 37
688
            $this->_sql = substr($this->_sql, 0, -1);
689 36
        } elseif ($paramType == DBConst::ARRAY) {
690
            foreach ($params as $p) {
691
                if (count($p) != count($arrFields)) {
692
                    $this->_logger->emergency("Inconsistent number of fields to values in extendedInsert", [
693 36
                        $p,
694 1
                        debug_backtrace()
695 1
                    ]);
696
                    throw new MissingOrInvalidParam("Inconsistent number of fields in fields and values in extendedInsert " . print_r($p, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($p, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

696
                    throw new MissingOrInvalidParam("Inconsistent number of fields in fields and values in extendedInsert " . /** @scrutinizer ignore-type */ print_r($p, true));
Loading history...
697 1
                }
698
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($p))) . ")";
699 35
700
                if ($p != end($params)) {
701
                    $this->_sql .= ",";
702
                }
703 36
            }
704
        } elseif ($paramType == DBConst::ARRAY_PRIMATIVE) {
705 34
            foreach ($params as $p) {
706 8
                $this->_sql .= "(".$this->_escape($p)."),";
707 8
            }
708 8
            $this->_sql = substr($this->_sql, 0, -1);
709 8
        } else {
710 1
            throw new InvalidArgumentException("Invalid param type in extendedInsert");
711
        }
712 8
713
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
714 8
            return $this->execute();
715 8
        }
716
717
        return $this->_sql;
718
    }
719
720 34
    /**
721 9
     * Build a statement to update a table
722 9
     *
723
     * @param string $strTableName
724
     *            The table name to update
725 32
     * @param mixed $params
726
     *            Name/value pairs of the field name and value
727
     * @param array:DBWhere|DBWhere $arrWhere
728
     *            [optional]
729 32
     *            DBWhere clauses
730
     * @param array $arrFlags
731
     *            [optional]
732
     *            Two-dimensional array to create other flag options (joins, order, and group)
733
     *
734
     * @see Database::flags()
735
     *
736
     * @return NULL|string
737
     *
738
     * @throws InvalidArgumentException
739
     * @throws MissingOrInvalidParam
740
     * @throws MissingInterfaceAndMethods
741
     *
742
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
743
     */
744
    public function update(string $strTableName, $params, $arrWhere = [], ?array $arrFlags = [])
745
    {
746
        $this->_sql = "UPDATE ";
747
        $this->setQueryType(DBConst::UPDATE);
748
        $this->checkTableName($strTableName);
749
750
        $this->_sql .= $strTableName;
751
752 4
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
753
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
754 4
            unset($arrFlags['joins']);
755 4
        }
756
757
        $this->_sql .= " SET ";
758 4
759 4
        $paramType = $this->checkParamType($params);
760
761
        if ($paramType == DBConst::ARRAY_PRIMATIVE || $paramType == DBConst::ARRAY) {
762
            $keys = array_keys($params);
763 4
            foreach ($params as $f => $p) {
764 1
                $field = $f;
765
                if ((strpos($f, "`") === false) &&
766
                    (strpos($f, ".") === false) &&
767
                    (strpos($f, "*") === false) &&
768 4
                    (stripos($f, " as ") === false)
769
                ) {
770 4
                    $field = "`{$f}`";
771 2
                }
772 2
773 2
                if (! is_null($p)) {
774 2
                    $this->_sql .= "$field={$this->_escape($p)}";
775 1
                } else {
776
                    $this->_sql .= "$field=NULL";
777 2
                }
778
779 2
                if ($f != end($keys)) {
780 2
                    $this->_sql .= ",";
781
                }
782
            }
783
        } elseif ($paramType == DBConst::OBJECT) {
784
            $key_value = $params->update();
785 4
            $fields = array_keys($key_value);
786 1
            $values = array_map([$this, '_escape'], array_values($key_value));
787
            foreach ($fields as $x => $f) {
788
                if ($x > 0) {
789 4
                    $this->_sql .= ",";
790
                }
791
                $this->_sql .= "`{$f}`={$values[$x]}";
792
            }
793 4
        }
794
795
        $where = $this->parseClause($arrWhere);
796
797
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_array($where) is always true.
Loading history...
introduced by
The condition is_null($where) is always false.
Loading history...
798
            $where_str = " WHERE";
799
            $this->_logger->debug("Parsing where clause and adding to query");
800
            foreach ($where as $x => $w) {
801
                if ($x > 0) {
802
                    $where_str .= " {$w->sqlOperator}";
803
                }
804
                $where_str .= $w;
805
            }
806
            if (strlen($where_str) > strlen(" WHERE")) {
807
                $this->_sql .= $where_str;
808
            }
809
        }
810 7
811
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
812 7
            return $this->execute();
813 7
        }
814
815
        return $this->_sql;
816 7
    }
817 7
818
    /**
819
     * Function to offer an extended updated functionality by using two different tables.
820 7
     *
821
     * @param string $strTableToUpdate
822
     *            The table that you want to update (alias 'tbu' is automatically added)
823 6
     * @param string $strOriginalTable
824 3
     *            The table with the data you want to overwrite to_be_updated table (alias 'o' is automatically added)
825 3
     * @param string $strLinkField
826 3
     *            The common index value between them that will join the fields
827 3
     * @param array|string $arrParams
828 3
     *            If string only a single field is updated (tbu.$params = o.$params)
829 3
     *            If array each element in the array is a field to be updated (tbu.$param = o.$param)
830 3
     *
831 3
     * @return mixed
832 1
     *
833 2
     * @throws InvalidArgumentException
834 1
     *
835 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
836 1
     */
837 1
    public function extendedUpdate(string $strTableToUpdate, string $strOriginalTable, string $strLinkField, $arrParams)
838 1
    {
839
        $this->_sql = "UPDATE ";
840 1
        $this->setQueryType(DBConst::EXTENDED_UPDATE);
841
842
        if (! is_null($strTableToUpdate) && ! is_null($strOriginalTable) && ! is_null($strLinkField)) {
0 ignored issues
show
introduced by
The condition is_null($strLinkField) is always false.
Loading history...
introduced by
The condition is_null($strTableToUpdate) is always false.
Loading history...
introduced by
The condition is_null($strOriginalTable) is always false.
Loading history...
843 5
            $this->_sql .= "$strTableToUpdate tbu INNER JOIN $strOriginalTable o USING ($strLinkField) SET ";
844
        }
845
846
        $paramType = $this->checkParamType($arrParams);
847 5
848
        if ($paramType == DBConst::ARRAY_PRIMATIVE) {
849
            foreach ($arrParams as $param) {
850
                if ($param != $strLinkField) {
851
                    $this->_sql .= "tbu.`$param` = o.`$param`,";
852
                }
853
            }
854
            $this->_sql = substr($this->_sql, 0, - 1);
855
        } elseif ($paramType == DBConst::STRING) {
856
            $this->_sql .= "tbu.`$arrParams` = o.`$arrParams`";
857
        } else {
858
            throw new InvalidArgumentException("Do not understand datatype " . gettype($arrParams), E_ERROR);
859
        }
860
861
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
862
            return $this->execute();
863
        }
864
865
        return $this->_sql;
866
    }
867
868
    /**
869
     * Function to build a replace query
870
     *
871 8
     * @param string $strTableName
872
     *            The table to update
873 8
     * @param mixed $arrParams
874 8
     *            Name/value pair to insert
875
     *
876
     * @return NULL|string
877 8
     *
878 8
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
879 8
     */
880 8
    public function replace(string $strTableName, $params)
881 8
    {
882
        $this->_sql = null;
883
        $this->setQueryType(DBConst::REPLACE);
884 8
        $this->checkTableName($strTableName);
885 6
886
        $this->_sql = "REPLACE INTO $strTableName ";
887 6
888 2
        $paramType = $this->checkParamType($params);
889 2
890 2
        if ($paramType == DBConst::ARRAY || $paramType == DBConst::ARRAY_PRIMATIVE) {
891
            $keys = array_keys($params);
892
            $vals = array_values($params);
893 2
894 4
            $this->_sql .= "(`" . implode("`,`", $keys) . "`)";
895
            $this->_sql .= " VALUES (" . implode(",", array_map([
896
                $this,
897 4
                '_escape'
898 4
            ], array_values($vals))) . ")";
899 4
        } elseif ($paramType == DBConst::OBJECT) {
900 2
            $key_value = $params->replace();
901 2
            $this->_sql .= "(`" . implode("`,`", array_keys($key_value)) . "`) VALUES ";
902 2
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . ")";
903
        } else {
904 2
            throw new MissingOrInvalidParam('Arrays or Objects that implement DBInterface are the only valid types for replace');
905
        }
906 3
907
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
908 3
            return $this->execute();
909 3
        }
910
911
        return $this->_sql;
912
    }
913
914
    /**
915
     * Function to build an extended replace statement
916 4
     *
917
     * @param string $strTableName
918
     *            Table name to update
919
     * @param array $arrFields
920 4
     *            Array of fields
921
     * @param mixed $arrParams
922
     *            Two-dimensional array of values
923
     *
924
     * @return NULL|string
925
     *
926
     * @throws InvalidArgumentException
927
     *
928
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
929
     */
930
    public function extendedReplace(string $strTableName, array $arrFields, $params)
931
    {
932
        $this->_sql = null;
933
        $this->setQueryType(DBConst::EXTENDED_REPLACE);
934
        $this->checkTableName($strTableName);
935
936
        if (! is_array($arrFields) || ! count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_array($arrFields) is always true.
Loading history...
937
            throw new InvalidArgumentException("Error with the field type");
938
        }
939
940
        $this->_sql = "REPLACE INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
941
942
        $paramType = $this->checkParamType($params);
943
        $this->_sql .= " VALUES ";
944
945
        if ($paramType == DBConst::ARRAY) {
946
            foreach ($params as $p) {
947 9
                $this->_sql .= "(" . implode(",", array_map([
948
                    $this,
949 9
                    '_escape'
950 9
                ], array_values($p))) . ")";
951
952 9
                if ($p != end($params)) {
953 9
                    $this->_sql .= ",";
954
                }
955 9
            }
956 1
        } elseif ($paramType == DBConst::COLLECTION || $paramType == DBConst::ARRAY_OBJECT) {
957 1
            if ($paramType == DBConst::COLLECTION) {
958
                $last = $params->last();
959
            } elseif ($paramType == DBConst::ARRAY_OBJECT) {
960 9
                $last = end($params);
961
            }
962
963 9
            foreach ($params as $p) {
964
                $key_value = $p->replace();
965 7
                $this->_sql .= "(" . implode(",", array_map([
966 6
                    $this,
967 6
                    '_escape'
968 6
                ], array_values($key_value))) . ")";
969 6
970 6
                if ($p != $last) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $last does not seem to be defined for all execution paths leading up to this point.
Loading history...
971 5
                    $this->_sql .= ",";
972 5
                }
973
            }
974 5
        } elseif ($paramType == DBConst::ARRAY_PRIMATIVE) {
975
            foreach ($params as $p) {
976
                $this->_sql .= "(".$this->_escape($p)."),";
977 6
            }
978 5
            $this->_sql = substr($this->_sql, 0, -1);
979
        }
980 1
981
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
982
            return $this->execute();
983 6
        }
984 1
985
        return $this->_sql;
986
    }
987 1
988 1
    /**
989 1
     * Function to build a delete statement
990 1
     *
991 1
     * @param string $strTableName
992 1
     *            Table name to act on
993 1
     * @param array $arrFields
994
     *            [optional]
995 1
     *            Optional list of fields to delete (used when including multiple tables)
996
     * @param array:DBWhere|DBWhere $arrWhere
997
     *            [optional]
998
     *            Optional where clauses to use
999
     * @param array $arrJoins
1000
     *            [optional]
1001 7
     *            Optional 2-dimensional array to add other flags
1002
     *
1003 7
     * @see Database::flags()
1004 3
     *
1005 3
     * @return string|NULL
1006 3
     *
1007 3
     * @throws InvalidArgumentException
1008 1
     *
1009
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1010 3
     */
1011
    public function delete(string $strTableName, ?array $arrFields = [], $arrWhere = [], ?array $arrJoins = [])
1012 3
    {
1013 3
        $this->_sql = "DELETE";
1014
        $this->setQueryType(DBConst::DELETE);
1015
        $this->checkTableName($strTableName);
1016
1017 7
        $this->_logger->debug("Deleting table data");
1018
1019
        if (! is_null($arrFields) && is_array($arrFields) && count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_array($arrFields) is always true.
Loading history...
introduced by
The condition is_null($arrFields) is always false.
Loading history...
1020
            $this->_sql .= " " . implode(",", $arrFields);
1021 7
        }
1022
1023
        $this->_sql .= " FROM $strTableName";
1024
1025 7
        if (! is_null($arrJoins) && is_array($arrJoins) && count($arrJoins)) {
0 ignored issues
show
introduced by
The condition is_array($arrJoins) is always true.
Loading history...
introduced by
The condition is_null($arrJoins) is always false.
Loading history...
1026
            $this->_sql .= " " . implode(" ", $arrJoins);
1027
        }
1028
1029
        $where = $this->parseClause($arrWhere);
1030
1031
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_array($where) is always true.
Loading history...
introduced by
The condition is_null($where) is always false.
Loading history...
1032
            $where_str = " WHERE";
1033
            $this->_logger->debug("Parsing where clause and adding to query");
1034
            foreach ($where as $x => $w) {
1035
                if ($x > 0) {
1036
                    $where_str .= " {$w->sqlOperator}";
1037
                }
1038
                $where_str .= $w;
1039
            }
1040
            if (strlen($where_str) > strlen(" WHERE")) {
1041
                $this->_sql .= $where_str;
1042
            }
1043
        }
1044
1045
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1046
            return $this->execute();
1047 2
        }
1048
1049 2
        return $this->_sql;
1050 2
    }
1051
1052 2
    /**
1053 2
     * Function to build a drop table statement (automatically executes)
1054
     *
1055
     * @param string $strTableName
1056 2
     *            Table to drop
1057 2
     * @param string $strType
1058 2
     *            [optional]
1059 2
     *            Type of item to drop ('table', 'view') (defaulted to 'table')
1060
     * @param bool $blnIsTemp
1061
     *            [optional]
1062 2
     *            Optional bool if this is a temporary table
1063
     *
1064
     * @return string|NULL
1065
     *
1066
     * @throws InvalidArgumentException
1067
     *
1068
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1069 2
     */
1070
    public function drop(string $strTableName, string $strType = 'table', bool $blnIsTemp = false)
1071
    {
1072
        $this->_sql = null;
1073 2
        $this->setQueryType(DBConst::DROP);
1074
        $this->checkTableName($strTableName);
1075
1076
        switch ($strType) {
1077
            case 'table':
1078
                $strType = 'TABLE';
1079
                break;
1080
            case 'view':
1081
                $strType = 'VIEW';
1082
                break;
1083
            default:
1084
                throw new InvalidArgumentException("Invalid type $strType", E_ERROR);
1085
        }
1086
1087
        $this->_sql = "DROP" . ($blnIsTemp ? " TEMPORARY" : "") . " $strType IF EXISTS `{$strTableName}`";
1088 2
1089
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1090 2
            return $this->execute();
1091 2
        }
1092
1093 2
        return $this->_sql;
1094 2
    }
1095
1096
    /**
1097 2
     * Function to build a truncate table statement (automatically executes)
1098
     *
1099 2
     * @param string $strTableName
1100 1
     *            Table to truncate
1101 1
     *
1102
     * @return string|NULL
1103 1
     *
1104 1
     * @throws InvalidArgumentException
1105 1
     *
1106 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1107 1
     */
1108 1
    public function truncate(string $strTableName)
1109 1
    {
1110 1
        $this->_sql = null;
1111 1
        $this->setQueryType(DBConst::TRUNCATE);
1112
        $this->checkTableName($strTableName);
1113
1114
        $this->_sql = "TRUNCATE TABLE $strTableName";
1115
1116 2
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1117
            return $this->execute();
1118
        }
1119
1120 2
        return $this->_sql;
1121
    }
1122
1123
    /**
1124
     * Function to build a create temporary table statement
1125
     *
1126
     * @param string $strTableName
1127
     *            Name to give the table when creating
1128
     * @param bool $blnIsTemp
1129
     *            [optional]
1130
     *            Optional bool to make the table a temporary table
1131
     * @param mixed $strSelect
1132
     *            [optional]
1133
     *            Optional parameter if null uses last built statement
1134
     *            If string, will be made the SQL statement executed to create the table
1135
     *            If array, 2-dimensional array with "field", "datatype" values to build table fields
1136
     *
1137
     * @return NULL|string
1138
     *
1139 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1140
     */
1141 1
    public function createTable(string $strTableName, bool $blnIsTemp = false, $strSelect = null)
1142 1
    {
1143
        $this->setQueryType(DBConst::CREATE_TABLE);
1144 1
1145
        $this->checkTableName($strTableName);
1146
1147
        if (is_null($strSelect) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1148 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($this->_sql)";
1149 1
        } elseif (is_string($strSelect)) {
1150
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($strSelect)";
1151
        } elseif (is_array($strSelect)) {
1152 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName (";
1153 1
1154
            foreach ($strSelect as $field) {
1155 1
                $default = null;
1156 1
                if (is_a($field, 'Godsgood33\Php_Db\DBCreateTable')) {
1157 1
                    $this->_sql .= (string) $field . ",";
1158 1
                } elseif (is_array($field)) {
1159 1
                    if (isset($field['default'])) {
1160 1
                        $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1161
                    }
1162 1
                    $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1163 1
                }
1164
            }
1165
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1166
        }
1167
1168
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1169
            return $this->execute();
1170
        }
1171
1172
        return $this->_sql;
1173
    }
1174
1175
    /**
1176
     * Function to create a table using a stdClass object derived from JSON
1177
     *
1178
     * @param stdClass $json
1179
     * @param bool $blnDropFirst bool to decide if you want to drop the table first
1180 1
     *
1181
     * @example /examples/create_table_json.json
1182
     *
1183
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1184 1
     */
1185
    public function createTableJson($json, bool $blnDropFirst = false)
1186
    {
1187
        $this->setQueryType(DBConst::CREATE_TABLE);
1188
        $this->_c->select_db($json->schema);
1189
1190
        if ($blnDropFirst) {
1191
            $this->drop($json->name);
1192
            if (!defined('PHP_DB_AUTORUN') || !PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1193
                $this->execute();
1194
            }
1195
        }
1196
1197
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1198
        foreach ($json->fields as $field) {
1199
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1200
1201
            if ($field->dataType == 'enum' && isset($field->values)) {
1202
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1203
            }
1204
1205
            if (isset($field->ai) && $field->ai) {
1206
                $this->_sql .= " AUTO_INCREMENT";
1207
            }
1208
1209
            if (isset($field->nn) && $field->nn) {
1210 4
                $this->_sql .= " NOT NULL";
1211
            } elseif (isset($field->default)) {
1212 4
                if (strtolower($field->default) == 'null') {
1213 4
                    $this->_sql .= " DEFAULT NULL";
1214
                } elseif (strlen($field->default)) {
1215 4
                    $this->_sql .= " DEFAULT '{$field->default}'";
1216
                }
1217 4
            }
1218 1
1219
            if ($field != end($json->fields)) {
1220
                $this->_sql .= ",";
1221 4
            }
1222 4
        }
1223
1224
        if (isset($json->index) && count($json->index)) {
1225 4
            foreach ($json->index as $ind) {
1226 1
                $ref = null;
1227
                if (is_array($ind->ref)) {
1228
                    $ref = "";
1229 4
                    foreach ($ind->ref as $r) {
1230
                        $ref .= "`{$r}` ASC,";
1231 4
                    }
1232 2
                    $ref = substr($ref, 0, -1);
1233 2
                } elseif (is_string($ind->ref)) {
1234 2
                    $ref = $ind->ref;
1235 2
                }
1236 1
                if (!is_null($ref)) {
1237
                    $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ref}`)";
1238 2
                }
1239
            }
1240 2
        }
1241 2
1242
        if (isset($json->constraints) && count($json->constraints)) {
1243
            foreach ($json->constraints as $con) {
1244
                $this->_sql .= ", CONSTRAINT `{$con->id}` " . "FOREIGN KEY (`{$con->local}`) " . "REFERENCES `{$con->schema}`.`{$con->table}` (`{$con->field}`) " . "ON DELETE " . (is_null($con->delete) ? "NO ACTION" : strtoupper($con->delete)) . " " . "ON UPDATE " . (is_null($con->update) ? "NO ACTION" : strtoupper($con->update));
1245 4
            }
1246
        }
1247
1248
        if (isset($json->unique) && count($json->unique)) {
1249 4
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1250
        }
1251
1252
        if (isset($json->primary_key) && count($json->primary_key)) {
1253
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1254
        } else {
1255
            if (substr($this->_sql, - 1) == ',') {
1256
                $this->_sql = substr($this->_sql, 0, - 1);
1257
            }
1258
1259
            $this->_sql .= ")";
1260
        }
1261
1262
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
1263
            return $this->execute();
1264
        }
1265
1266
        return $this->_sql;
1267
    }
1268
1269
    /**
1270 5
     * Method to add a column to the database (only one at a time!)
1271
     *
1272 5
     * @param string $strTableName
1273 5
     * @param stdClass $params
1274
     *
1275 5
     * @return string|mixed
1276 5
     *
1277 4
     * @throws InvalidArgumentException
1278 4
     *
1279 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1280 1
     */
1281 1
    public function addColumn(string $strTableName, stdClass $params)
1282
    {
1283
        $this->setQueryType(DBConst::ALTER_TABLE);
1284
        $this->checkTableName($strTableName);
1285
1286 5
        $this->_sql = "ALTER TABLE {$strTableName} ADD COLUMN";
1287 5
1288
        if (!self::checkObject($params, ['name', 'dataType'])) {
1289
            $this->_logger->error("Missing elements for the addColumn method (need 'name', 'dataType')", [$params]);
1290 5
            throw new InvalidArgumentException("Missing elements for the addColumn method");
1291
        }
1292
1293
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1294 5
        $default = null;
1295
        if ($params->default === null) {
1296
            $default = " DEFAULT NULL";
1297
        } elseif (strlen($params->default)) {
1298
            $default = " DEFAULT {$this->_escape($params->default)}";
1299
        }
1300
        $this->_sql .= " `{$params->name}` {$params->dataType}" . $nn . $default;
1301
1302
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1303
            return $this->execute();
1304
        }
1305
1306
        return $this->_sql;
1307
    }
1308
1309 1
    /**
1310
     * Method to drop a fields from a table
1311 1
     *
1312 1
     * @param string $strTableName
1313
     * @param string|array:string $params
1314 1
     *
1315 1
     * @return string|mixed
1316
     *
1317
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1318 1
     */
1319
    public function dropColumn(string $strTableName, $params)
1320
    {
1321
        $this->setQueryType(DBConst::ALTER_TABLE);
1322 1
        $this->_sql = "ALTER TABLE {$strTableName} DROP COLUMN";
1323
1324
        if (is_array($params) && count($params)) {
1325
            foreach ($params as $col) {
1326
                $this->_sql .= " `{$col->name}`";
1327
1328
                if ($col != end($params)) {
1329
                    $this->_sql .= ",";
1330
                }
1331
            }
1332
        } elseif (is_string($params)) {
1333
            $this->_sql .= " `{$params}`";
1334
        }
1335
1336
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1337
            return $this->execute();
1338
        }
1339
1340
        return $this->_sql;
1341
    }
1342
1343 4
    /**
1344
     * Method to modify a field to change it's datatype, name, or other parameter
1345 4
     *
1346
     * @param string $strTableName
1347 4
     * @param stdClass $params
1348 1
     *
1349 3
     * @return string|mixed
1350 1
     *
1351 2
     * @throws InvalidArgumentException
1352 2
     *
1353
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1354 2
     */
1355 2
    public function modifyColumn($strTableName, $params)
1356 2
    {
1357 2
        $this->setQueryType(DBConst::ALTER_TABLE);
1358
        $this->_sql = "ALTER TABLE {$strTableName} MODIFY COLUMN";
1359
1360
        if (!self::checkObject($params, ['name', 'dataType'])) {
1361
            $this->_logger->error("Missing elements to the modifyColumn method (need 'name' and 'dataType')", [$params]);
1362
            throw new InvalidArgumentException("Missing elements to the modifyColumn method");
1363
        }
1364
1365 2
        if (!isset($params->new_name)) {
1366
            $params->new_name = $params->name;
1367
        }
1368 4
1369
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1370
        $default = null;
1371
        if ($params->default === null) {
1372 4
            $default = " DEFAULT NULL";
1373
        } elseif (strlen($params->default)) {
1374
            $default = " DEFAULT {$this->_escape($params->default)}";
1375
        }
1376
        $this->_sql .= " `{$params->name}` `{$params->new_name}` {$params->dataType}" . $nn . $default;
1377
1378
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1379
            return $this->execute();
1380
        }
1381
1382
        return $this->_sql;
1383
    }
1384
1385 3
    /**
1386
     * Method to add a constraint to a table
1387 3
     *
1388 3
     * @param string $strTableName
1389
     * @param stdClass $params
1390 3
     *
1391
     * @return string|mixed
1392
     *
1393
     * @throws InvalidArgumentException
1394
     *
1395
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1396
     */
1397 3
    public function addConstraint($strTableName, $params)
1398 3
    {
1399 3
        $this->setQueryType(DBConst::ALTER_TABLE);
1400
        $this->_sql = "ALTER TABLE {$strTableName} ADD CONSTRAINT";
1401 3
1402 1
        if (!is_a($params, 'stdClass')) {
1403
            $this->_logger->critical("Error in reading constraint field");
1404
            throw new InvalidArgumentException("Error in reading constraint field");
1405 3
        }
1406 3
1407
        if (!self::checkObject($params, ['id', 'local', 'schema', 'table', 'field', 'delete', 'update'])) {
1408
            $this->_logger->error("Missing elements in the addConstraint method (need 'id', 'local', 'schema', 'table', 'field', 'delete', 'update')", [$params]);
1409 3
            throw new InvalidArgumentException("There are some missing elements for the addConstraint action");
1410 3
        }
1411 2
1412 2
        if (!in_array(strtoupper($params->delete), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1413 1
            $this->_logger->error("Invalid action for deletion on addConstraint");
1414 1
            throw new InvalidArgumentException("Invalid action for deletion on addConstraint");
1415 1
        }
1416
1417
        if (!in_array(strtoupper($params->update), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1418
            $this->_logger->error("Invalid action for update on addConstraint");
1419 3
            throw new InvalidArgumentException("Invalid action for update on addConstraint");
1420 2
        }
1421
1422
        if (is_array($params->field) && is_array($params->local)) {
1423
            $field = "`" . implode("`,`", $params->field) . "`";
1424 3
            $local = "`" . implode("`,`", $params->local) . "`";
1425 1
        } elseif (is_string($params->field) && is_string($params->local)) {
1426 1
            $field = "`{$params->field}`";
1427 1
            $local = "`{$params->local}`";
1428
        } else {
1429
            throw new InvalidArgumentException("Invalid type for the field and local values both must be an array or string");
1430
        }
1431
        $this->_sql .= " `{$params->id}` FOREIGN KEY ({$local}) REFERENCES `{$params->schema}`.`{$params->table}` ({$field}) ON DELETE {$params->delete} ON UPDATE {$params->update}";
1432
1433 1
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1434 1
            return $this->execute();
1435
        }
1436 1
1437 1
        return $this->_sql;
1438
    }
1439
1440
    /**
1441
     * Check to see if a field in a table exists
1442 3
     *
1443
     * @param string $strTableName
1444
     *            Table to check
1445
     * @param string $strFieldName
1446
     *            Field name to find
1447
     *
1448 3
     * @return bool Returns TRUE if field is found in that schema and table, otherwise FALSE
1449 1
     */
1450
    public function fieldExists(string $strTableName, string $strFieldName): bool
1451
    {
1452 3
        $fdata = $this->fieldData($strTableName);
1453 3
1454
        if (is_array($fdata) && count($fdata)) {
1455
            foreach ($fdata as $field) {
1456
                if ($field->name == $strFieldName) {
1457
                    return true;
1458
                }
1459
            }
1460
        }
1461
1462 3
        return false;
1463
    }
1464
1465
    /**
1466 3
     * Function to get the column data (datatype, flags, defaults, etc)
1467
     *
1468
     * @param string $strTableName
1469
     *            Table to query
1470
     * @param mixed $field
1471
     *            [optional]
1472
     *            Optional field to retrieve data (if null, returns data from all fields)
1473
     *
1474
     * @return mixed
1475
     */
1476
    public function fieldData(string $strTableName, $field = null)
1477
    {
1478
        if (is_null($field)) {
1479
            $res = $this->_c->query("SELECT * FROM $strTableName LIMIT 1");
1480
        } elseif (is_array($field)) {
1481 3
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $strTableName LIMIT 1");
1482
        } elseif (is_string($field)) {
1483 3
            $res = $this->_c->query("SELECT $field FROM $strTableName LIMIT 1");
1484 3
        } else {
1485
            return null;
1486 3
        }
1487
1488 3
        $fields = null;
1489 1
        if (is_a($res, 'mysqli_result')) {
1490 1
            $fields = $res->fetch_fields();
1491
            foreach ($fields as $i => $f) {
1492
                $fields["{$f->name}"] = $f;
1493 2
                unset($fields[$i]);
1494 2
            }
1495 2
        }
1496 1
1497 1
        return $fields;
1498 1
    }
1499
1500 2
    /**
1501
     * Function to check that all field parameters are set correctly
1502 2
     *
1503
     * @param stdClass $field_data
1504
     * @param stdClass $check
1505
     * @param array $pks
1506 2
     * @param stdClass $index
1507
     *
1508
     * @return array|string
1509
     */
1510
    public function fieldCheck(stdClass $field_data, stdClass $check, array $pks, ?array $index)
1511
    {
1512
        $default = null;
1513
        $ret = null;
1514
1515
        $nn = (isset($check->nn) && $check->nn ? " NOT NULL" : null);
1516
1517
        if ($check->default === null) {
1518
            $default = " DEFAULT NULL";
1519 3
        } elseif (strlen($check->default)) {
1520
            $default = " DEFAULT '{$check->default}'";
1521 3
        }
1522 3
1523
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1524 3
            $this->_logger->notice("Wrong datatype", [
1525 2
                'name' => $field_data->name,
1526 2
                'datatype' => $check->dataType
1527
            ]);
1528 2
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1529 1
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1530
            $this->_logger->notice("Incorrect size", [
1531
                'name' => $field_data->name,
1532 1
                'current' => $field_data->length,
1533 1
                'new_size' => $check->length
1534
            ]);
1535
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1536 3
        } elseif ($check->type == MYSQLI_TYPE_ENUM && ! ($field_data->flags & MYSQLI_ENUM_FLAG)) {
1537
            $this->_logger->notice("Setting ENUM type", [
1538
                'name' => $field_data->name,
1539
                'values' => implode(",", $check->values)
1540 3
            ]);
1541
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1542
        }
1543
1544
        if (! is_null($index) && count($index)) {
0 ignored issues
show
introduced by
The condition is_null($index) is always false.
Loading history...
1545
            foreach ($index as $ind) {
1546
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1547
                    $this->_logger->debug("Missing index", [
1548
                        'name' => $field_data->name
1549
                    ]);
1550
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1551
                }
1552
            }
1553
        }
1554
1555 3
        if (in_array($check->name, $pks) && ! ($field_data->flags & MYSQLI_PRI_KEY_FLAG)) {
1556
            $this->_logger->debug("Setting PKs", [
1557 3
                'keys' => implode(',', $pks)
1558 3
            ]);
1559
            $ret .= ($ret ? "," : "") . " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode("`,`", $pks) . "`)";
1560 3
        }
1561 1
1562 1
        return $ret;
1563
    }
1564
1565 2
    /**
1566 1
     * Function to check for the existence of a table within a schema
1567
     *
1568
     * @param string $strSchema
1569 2
     *            The schema to search in
1570 2
     * @param string $strTableName
1571 2
     *            Table to search for
1572 1
     *
1573 1
     * @return int|bool Returns number of tables that match if table is found in that schema, otherwise FALSE
1574 1
     *
1575
     * @throws InvalidArgumentException
1576 2
     */
1577
    public function tableExists($strSchema, $strTableName)
1578 2
    {
1579
        if (! $this->_c->select_db($strSchema)) {
1580
            $this->_logger->error("Schema {$strSchema} not found", [$this->_c->error]);
1581
            throw new InvalidArgumentException("Error connecting to schema {$strSchema}");
1582 2
        }
1583
1584
        if (preg_match("/[^0-9a-zA-Z\%\?\_]/", $strTableName)) {
1585
            $this->_logger->warning("Invalid table name {$strTableName}");
1586
            return false;
1587
        }
1588
1589
        $sql = "SHOW TABLES LIKE '{$strTableName}'";
1590
1591
        if ($res = $this->_c->query($sql)) {
1592
            if (gettype($res) == 'object' && is_a($res, 'mysqli_result') && $res->num_rows) {
1593
                return $res->num_rows;
1594
            }
1595
        } elseif ($this->_c->errno) {
1596
            $this->_logger->error($this->_c->error);
1597 7
        }
1598
1599 7
        return false;
1600 7
    }
1601
1602 7
    /**
1603 1
     * Function to detect if string is a JSON object or not
1604 1
     *
1605
     * @param string $strVal
1606
     *
1607 6
     * @return bool
1608 1
     */
1609 1
    public function isJson($strVal): bool
1610
    {
1611
        json_decode($strVal);
1612 5
        return (json_last_error() == JSON_ERROR_NONE);
1613 1
    }
1614 1
1615
    /**
1616
     * Function to escape SQL characters to prevent SQL injection
1617 4
     *
1618 1
     * @param mixed $val
1619 1
     *            Value to escape
1620
     * @param bool $blnEscape
1621
     *            Decide if we should escape or not
1622 3
     *
1623 1
     * @throws Exception
1624 1
     * @throws InvalidArgumentException
1625 2
     *
1626 1
     * @return string Escaped value
1627 1
     */
1628
    public function _escape($val, bool $blnEscape = true): string
1629 1
    {
1630
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1631 2
            return 'NULL';
1632
        } elseif (is_numeric($val) || is_string($val)) {
1633 2
            if ($blnEscape) {
1634
                return "'{$this->_c->real_escape_string($val)}'";
1635
            }
1636
            return $val;
1637 2
        } elseif (is_a($val, 'DateTime')) {
1638
            return "'{$val->format(MYSQL_DATETIME)}'";
1639
        } elseif (is_bool($val)) {
1640
            return $val ? "'1'" : "'0'";
1641
        } elseif (is_array($val)) {
1642
            $ret = [];
1643
            foreach ($val as $v) {
1644
                $ret[] = $this->_escape($v);
1645
            }
1646
            return "(" . implode(",", $ret) . ")";
1647
        } elseif (is_object($val) && method_exists($val, '_escape')) {
1648
            $ret = call_user_func([
1649
                $val,
1650 2
                '_escape'
1651
            ]);
1652 2
            if ($ret !== false && is_string($ret)) {
1653
                return $ret;
1654 2
            } else {
1655 2
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1656 2
            }
1657 1
        }
1658
1659
        throw new InvalidArgumentException("Unknown datatype to escape in SQL string {$this->_sql} " . gettype($val), E_ERROR);
1660
    }
1661
1662 1
    /**
1663
     * Function to populate the fields for the SQL
1664
     *
1665
     * @param mixed $fields
1666
     *            [optional]
1667
     *            Optional array of fields to string together to create a field list
1668
     *
1669
     * @return string|null
1670
     *
1671
     * @throws InvalidArgumentException
1672
     */
1673
    protected function fields($fields = null): string
1674
    {
1675
        $ret = null;
1676 7
1677
        if (is_array($fields) && count($fields) && isset($fields[0]) && is_string($fields[0])) {
1678 7
            foreach ($fields as $field) {
1679 3
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false) && stripos($field, 'IF(') === false) {
1680 5
                    $ret .= "`$field`,";
1681 5
                } else {
1682 1
                    $ret .= "$field,";
1683 1
                }
1684
            }
1685 1
            $ret = substr($ret, -1) == ',' ? substr($ret, 0, -1) : $ret;
0 ignored issues
show
Bug introduced by
It seems like $ret can also be of type null; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1685
            $ret = substr(/** @scrutinizer ignore-type */ $ret, -1) == ',' ? substr($ret, 0, -1) : $ret;
Loading history...
1686
        } elseif (is_a($fields, 'Godsgood33\Php_Db\DBField')) {
1687
            $ret = (string) $fields;
1688 7
        } elseif (is_array($fields) && count($fields) && isset($fields[0]) && is_a($fields[0], 'Godsgood33\Php_Db\DBField')) {
1689 7
            foreach ($fields as $f) {
1690 7
                $ret .= (string) $f.",";
1691 7
            }
1692 7
            $ret = rtrim($ret, ',');
0 ignored issues
show
Bug introduced by
It seems like $ret can also be of type null; however, parameter $string of rtrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1692
            $ret = rtrim(/** @scrutinizer ignore-type */ $ret, ',');
Loading history...
1693 7
        } elseif (is_string($fields)) {
1694
            $ret = $fields;
1695
        } elseif (is_null($fields)) {
1696
            $ret = '*';
1697 7
        } else {
1698
            throw new InvalidArgumentException("Invalid field type");
1699
        }
1700
1701
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
1702
    }
1703
1704
    /**
1705
     * Function to parse the flags
1706
     *
1707
     * @param array $flags
1708
     *            Two-dimensional array to added flags
1709
     *
1710 4
     *            <code>
1711
     *            [
1712 4
     *            &nbsp;&nbsp;'group' => 'field',
1713 4
     *            &nbsp;&nbsp;'having' => 'field',
1714
     *            &nbsp;&nbsp;'order' => 'field',
1715 4
     *            &nbsp;&nbsp;'start' => 0,
1716
     *            &nbsp;&nbsp;'limit' => 0
1717 4
     *            ]
1718 1
     *            </code>
1719 3
     *
1720 1
     * @see Database::groups()
1721
     * @see Database::having()
1722
     * @see Database::order()
1723 4
     *
1724 3
     * @return string
1725 3
     */
1726 3
    protected function flags(array $arrFlags)
1727
    {
1728 3
        $ret = '';
1729 1
1730 1
        if (isset($arrFlags['group'])) {
1731 1
            $ret .= $this->groups($arrFlags['group']);
1732 1
        }
1733 1
1734
        if (isset($arrFlags['having']) && is_array($arrFlags['having'])) {
1735 1
            $having = " HAVING";
1736
            $this->_logger->debug("Parsing where clause and adding to query");
1737
            foreach ($arrFlags['having'] as $x => $h) {
1738
                if ($x > 0) {
1739
                    $having .= " {$h->sqlOperator}";
1740
                }
1741
                $having .= $h;
1742
            }
1743
            if (strlen($having) > strlen(" HAVING")) {
1744 4
                $ret .= $having;
1745
            }
1746
        }
1747
1748
        if (isset($arrFlags['order'])) {
1749
            $ret .= $this->order($arrFlags['order']);
1750
        }
1751
1752
        if (isset($arrFlags['limit']) && (is_string($arrFlags['limit']) || is_numeric($arrFlags['limit']))) {
1753
            $ret .= " LIMIT ";
1754
            if (isset($arrFlags['start']) && (is_string($arrFlags['start']) || is_numeric($arrFlags['start']))) {
1755 4
                $ret .= "{$arrFlags['start']},";
1756
            }
1757
            $ret .= "{$arrFlags['limit']}";
1758
        }
1759
1760
        return $ret;
1761
    }
1762 4
1763
    /**
1764
     * Function to parse SQL GROUP BY statements
1765
     *
1766
     * @param mixed $groups
1767
     *
1768
     * @return string
1769
     *
1770
     * @throws InvalidArgumentException
1771
     */
1772
    protected function groups($groups): string
1773
    {
1774
        $ret = '';
1775
        if (is_array($groups) && count($groups)) {
1776
            $ret .= " GROUP BY";
1777 5
1778
            foreach ($groups as $grp) {
1779 5
                $ret .= " $grp";
1780 1
1781 1
                if ($grp != end($groups)) {
1782
                    $ret .= ",";
1783
                }
1784 4
            }
1785 1
        } elseif (is_string($groups)) {
1786 1
            $ret .= " GROUP BY {$groups}";
1787
        } else {
1788
            throw new InvalidArgumentException("Error in datatype for groups " . gettype($groups), E_ERROR);
1789 3
        }
1790
1791 3
        return $ret;
1792 3
    }
1793 2
1794
    /**
1795
     * Function to parse SQL ORDER BY statements
1796
     *
1797
     * @param mixed $order
1798
     *
1799 1
     * @return string
1800
     *
1801
     * @throws InvalidArgumentException
1802
     */
1803
    protected function order($order): string
1804
    {
1805
        $ret = '';
1806
        if (is_array($order)) {
1807
            $ret .= " ORDER BY";
1808
1809 1
            foreach ($order as $ord) {
1810
                $ret .= " {$ord['field']} {$ord['sort']}";
1811 1
1812 1
                if ($ord != end($order)) {
1813
                    $ret .= ",";
1814
                }
1815
            }
1816
        } elseif (is_string($order)) {
1817
            $ret .= " ORDER BY {$order}";
1818
        } else {
1819
            throw new InvalidArgumentException("Error in datatype for order method ".gettype($order), E_ERROR);
1820
        }
1821
1822
        return $ret;
1823
    }
1824
1825
    /**
1826
     * Method to check if there are any invalid characters in the table name
1827
     *
1828 131
     * @param string $strTableName
1829
     *      Table name passed in
1830 131
     *
1831 1
     * @throws MissingOrInvalidParam
1832 131
     *      Throws exception if there is a problem with the table name
1833 131
     */
1834 131
    private function checkTableName(string $strTableName)
1835
    {
1836 1
        if (!strlen($strTableName) || preg_match("/[^0-9a-zA-Z\$\_\ ]/", $strTableName)) {
1837 6
            throw new MissingOrInvalidParam("Invalid table name $strTableName");
1838 1
        }
1839 5
    }
1840 1
1841 4
    /**
1842 1
     * Function to see if a constraint exists
1843 1
     *
1844 1
     *
1845
     *
1846 1
     * @param string $strConstraintId
1847 3
     *
1848 2
     * @return bool
1849 2
     */
1850 2
    public function isConstraint($strConstraintId): bool
1851
    {
1852 2
        $res = $this->_c->query("SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = '{$strConstraintId}'");
1853 1
1854
        if ($res->num_rows) {
1855 1
            return true;
1856
        }
1857
1858
        return false;
1859 1
    }
1860
1861
    /**
1862
     * Method to add a where clause
1863
     *
1864
     * @param DBWhere|array:DBWhere $where
1865
     *
1866
     * @return bool|array:DBWhere
1867
     */
1868
    public function parseClause($where)
1869
    {
1870
        $ret = [];
1871
        $interfaces = [];
1872
        if (is_object($where)) {
1873 37
            $interfaces = class_implements($where);
1874
        }
1875 37
        if (is_array($where)) {
1876
            foreach ($where as $k => $w) {
1877 37
                if (!is_a($w, 'Godsgood33\Php_Db\DBWhere')) {
1878 3
                    return false;
1879 3
                }
1880 2
                $v = $this->_escape($w->value, $w->escape);
1881
                $where[$k]->value = $v;
1882 1
1883
                $ret[] = $where[$k];
1884
            }
1885 3
        } elseif (is_a($where, 'Godsgood33\Php_Db\DBWhere')) {
1886 34
            $v = $this->_escape($where->value, $where->escape);
1887 4
            $where->value = $v;
1888 30
            $ret[] = $where;
1889 29
        } elseif (in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable([$where, 'where'])) {
1890
            $ret = $this->parseClause($where->where());
1891 1
        } else {
1892
            $this->_logger->warning("Failed to get where", [$where]);
1893
        }
1894 36
1895
        return $ret;
1896
    }
1897
1898
    /**
1899
     * Encryption algorithm
1900
     *
1901
     * @param string $data
1902
     * @param string $salt
1903
     *
1904
     * @return string
1905
     *
1906
     * @throws Exception
1907
     *
1908
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
1909
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
1910
     */
1911
    public static function encrypt(string $data, ?string $salt = null)
1912
    {
1913
        if (!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
1914
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
1915
        }
1916
1917
        // Remove the base64 encoding from our key
1918
        if (is_null($salt)) {
1919 10
            $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
1920
        } else {
1921 10
            $encryption_key = base64_decode($salt);
1922
        }
1923 10
        // Generate an initialization vector
1924 3
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(PHP_DB_ENCRYPT_ALGORITHM));
1925
        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
1926
        $encrypted = openssl_encrypt($data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
1927 9
        // The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
1928 1
        return base64_encode($encrypted . '::' . $iv);
1929 1
    }
1930 1
1931 1
    /**
1932 1
     * Decryption algorithm
1933
     *
1934 1
     * @param string $data
1935
     *
1936 1
     * @return string
1937 1
     *
1938
     * @throws Exception
1939
     *
1940
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
1941 9
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
1942 3
     */
1943
    public static function decrypt(string $data)
1944
    {
1945 8
        if (!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
1946 1
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
1947 1
        }
1948 1
1949
        // Remove the base64 encoding from our key
1950 1
        $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
1951
1952
        // To decrypt, split the encrypted data from our IV - our unique separator used was "::"
1953 8
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
1954
        $plaintext = openssl_decrypt($encrypted_data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
1955
        return $plaintext;
1956
    }
1957
1958
    /**
1959
     * Method to check if all required fields are available in the object
1960
     *
1961
     * @param object $object
1962
     * @param array:string $requiredFields
1963
     *
1964
     * @return bool
1965 3
     */
1966
    public static function checkObject($object, $requiredFields): bool
1967 3
    {
1968 3
        $haystack = array_keys(json_decode(json_encode($object), true));
1969 1
        foreach ($requiredFields as $r) {
1970
            if (!in_array($r, $haystack)) {
1971 1
                return false;
1972 1
            }
1973
        }
1974 1
1975 1
        return true;
1976
    }
1977
1978 2
    /**
1979 1
     * Method to retrieve the error data
1980
     *
1981 1
     * @return string
1982
     */
1983
    public function error(): string
1984 2
    {
1985
        return $this->_c->error;
1986
    }
1987
1988
    /**
1989
     * Method to check the parameter types
1990
     *
1991
     * @param mixed $param
1992
     *
1993
     * @return int
1994
     *
1995
     * @throws MissingInterfaceAndMethods
1996 3
     * @throws Exception
1997
     */
1998 3
    private function checkParamType($param): int
1999 3
    {
2000 1
        // check for implented object interfaces
2001
        $interfaces = is_object($param) ? class_implements($param) : [];
2002 1
2003 1
        // numeric is the only datatype we can't have
2004
        if (is_numeric($param)) {
2005 1
            throw new MissingOrInvalidParam('Numeric parameters are not valid');
2006 1
        } elseif (is_resource($param)) {
2007
            throw new MissingOrInvalidParam('Resources are invalid parameters');
2008
        }
2009 2
2010 1
        // check for a SELECT statement within an insert
2011
        if (is_string($param) && stripos($param, 'select') !== false) {
2012 1
            return DBConst::STRING_SELECT;
2013
        } elseif (is_string($param)) {
2014
            return DBConst::STRING;
2015 2
        }
2016
        // param is an object, check to see if it includes the required interface
2017
        elseif (is_object($param) && !is_iterable($param)) {
2018
            if (!in_array('Godsgood33\Php_Db\DBInterface', $interfaces)) {
2019
                throw new MissingInterfaceAndMethods('Object does not implement DBInterface interface');
2020
            }
2021
2022
            return DBConst::OBJECT;
2023
        }
2024
        // param is an array
2025
        elseif (is_array($param)) {
2026
            // check that there is actual data in the array
2027 83
            if (!count($param)) {
2028
                throw new Exception('Array param is empty');
2029 83
            }
2030
2031
            // check the first element of the array and see if it's an object then recurse through to check it
2032
            $first = array_shift($param);
2033
            if (is_object($first) && $this->checkParamType($first) == DBConst::OBJECT) {
2034
                return DBConst::ARRAY_OBJECT;
2035
            } elseif (is_numeric($first) || is_string($first)) {
2036
                return DBConst::ARRAY_PRIMATIVE;
2037
            }
2038
2039
            return DBConst::ARRAY;
2040
        }
2041
        // object is potentially a collection
2042
        elseif (is_object($param)) {
2043
            // check that collection has required interface
2044
            if (!in_array('IteratorAggregate', $interfaces)) {
2045
                throw new MissingInterfaceAndMethods('Object does not implement IteratorAggregate interface');
2046
            }
2047
2048
            // get the first element in the collection
2049
            $it = $param->getIterator();
2050
            $first = $it->current();
2051
2052
            // check that the first element of the collection is a valid object as defined above
2053
            if ($this->checkParamType($first) == DBConst::OBJECT) {
2054
                return DBConst::COLLECTION;
2055
            }
2056
        }
2057
2058
        return 0;
2059 51
    }
2060
2061 51
    /**
2062 51
     * Method to check the log path
2063 51
     *
2064 8
     * @param string $strLogPath
2065
     *      The absolute file path of the log file
2066 51
     */
2067 42
    private function checkLog(string $strLogPath)
2068 10
    {
2069 1
        // set the log file path
2070
        $this->_logPath = $strLogPath;
2071 9
        if (!file_exists($this->_logPath)) {
2072 9
            touch($this->_logPath . "/db.log");
2073
        }
2074 9
    }
2075
2076 9
    /**
2077 8
     * Method to check the log level
2078 6
     *
2079 6
     * @param int $intLogLevel
2080 2
     */
2081 1
    private function checkLogLevel(?int $intLogLevel)
2082
    {
2083 1
        // set the log level
2084
        if (!defined("PHP_DB_LOG_LEVEL") && is_null($intLogLevel)) {
2085
            $this->_logLevel = Logger::ERROR;
2086 48
        } elseif (!is_null($intLogLevel)) {
2087
            $this->_logLevel = $intLogLevel;
2088
        } elseif (defined('PHP_DB_LOG_LEVEL')) {
2089
            $this->_logLevel = PHP_DB_LOG_LEVEL;
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_LOG_LEVEL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2090
        }
2091
    }
2092
2093
    /**
2094
     * Method to initialize the log stream
2095
     */
2096
    private function initLog()
2097
    {
2098
        // open the log handler
2099
        $this->_logger = new Logger('db', [
2100
            new StreamHandler(realpath($this->_logPath . "/db.log"), $this->_logLevel)
2101
        ]);
2102 1
2103
        // check to see if we are operating in a CLI and if the user wants log data output to the terminal
2104 1
        if (PHP_SAPI == 'cli' && defined('PHP_DB_CLI_LOG') && PHP_DB_CLI_LOG) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_CLI_LOG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2105
            $stream = new StreamHandler(STDOUT, $this->_logLevel);
2106
            $stream->setFormatter(new LineFormatter("%datetime% %level_name% %message%" . PHP_EOL, "H:i:s.u"));
2107
            $this->_logger->pushHandler($stream);
2108
        }
2109 1
    }
2110 1
2111
    /**
2112
     * Method to check the required params for opening the db connection
2113
     */
2114
    private function checkRequiredParams()
2115 1
    {
2116
        // check to see if a connection was passed and all defined constants are present to establish a connection
2117 1
        if (!defined('PHP_DB_SERVER') || !defined('PHP_DB_USER') || !defined('PHP_DB_PWD') || !defined('PHP_DB_SCHEMA')) {
2118
            $this->_logger->critical("Missing essential defined constants");
2119 1
            throw new MissingOrInvalidParam("Please create and include a constant file with the following constants defining your DB connection (PHP_DB_SERVER, PHP_DB_USER, PHP_DB_PWD, PHP_DB_SCHEMA)", E_USER_ERROR);
2120
        } elseif (defined('PHP_DB_ENCRYPT') && (!defined('PHP_DB_ENCRYPT_ALGORITHM') || !defined('PHP_DB_ENCRYPT_SALT'))) {
2121
            $this->_logger->critical("Missing essential encryption constants");
2122
            throw new MissingOrInvalidParam("Missing required PHP_DB_ENCRYPT_ALGORITHM or PHP_DB_ENCRYPT_SALT constants");
2123
        }
2124
    }
2125
2126
    /**
2127
     * Method to init the connection
2128
     *
2129
     * @param mysqli $dbh
2130
     */
2131
    private function init(?mysqli $dbh)
2132
    {
2133
        // check to see if a connection was passed and all defined constants are present to establish a connection
2134 131
        if (! is_null($dbh) && is_a($dbh, 'mysqli')) {
2135
            $this->_logger->debug("Connecting through existing connection");
2136 131
            $this->_c = $dbh;
2137
            return;
2138
        }
2139
2140
        // check to see if the password is encrypted and decrypt if it is
2141 131
        if (defined('PHP_DB_ENCRYPT') && PHP_DB_ENCRYPT) {
2142
            $this->_logger->debug("Decrypting password");
2143
            $pwd = $this->decrypt(PHP_DB_PWD);
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_PWD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2144 131
        } else {
2145 131
            $pwd = PHP_DB_PWD;
2146 131
        }
2147
2148
        // open the connection
2149
        $this->_c = new mysqli(PHP_DB_SERVER, PHP_DB_USER, $pwd, PHP_DB_SCHEMA);
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SERVER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SCHEMA was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_USER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2150
2151
        // check for a connection error and throw an error if there is one
2152
        if ($this->_c->connect_errno) {
2153
            $this->_logger->error("Error connecting to database {$this->_c->connect_error}");
2154
            throw new ConnectError("Could not create database class due to error {$this->_c->connect_error}", E_ERROR);
2155
        }
2156
    }
2157
}
2158