Passed
Pull Request — master (#2)
by Ryan
06:34
created

Database   F

Complexity

Total Complexity 421

Size/Duplication

Total Lines 2215
Duplicated Lines 0 %

Test Coverage

Coverage 87.81%

Importance

Changes 24
Bugs 7 Features 2
Metric Value
eloc 820
c 24
b 7
f 2
dl 0
loc 2215
ccs 713
cts 812
cp 0.8781
rs 1.78
wmc 421

48 Methods

Rating   Name   Duplication   Size   Complexity  
A query() 0 3 2
A setVar() 0 17 3
C selectCount() 0 42 14
F update() 0 79 26
C select() 0 51 15
A getSchema() 0 10 2
A setSchema() 0 8 2
C extendedInsert() 0 50 13
A setLogger() 0 6 1
B insert() 0 38 8
B extendedUpdate() 0 27 11
B execute() 0 41 6
B replace() 0 33 6
A getLogLevel() 0 6 1
A setQueryType() 0 6 1
A getQueryType() 0 4 1
A isConnected() 0 11 2
C checkResults() 0 65 15
A __toString() 0 5 1
F __construct() 0 68 21
A groups() 0 20 6
A encrypt() 0 18 4
B dropColumn() 0 22 8
D createTable() 0 30 19
C flags() 0 35 14
A checkObject() 0 10 3
A fieldData() 0 22 6
A checkTableName() 0 3 3
B parseClause() 0 28 8
B addColumn() 0 26 8
F createTableJson() 0 82 34
A isJson() 0 4 1
C fields() 0 22 15
D checkParamType() 0 64 19
A fieldExists() 0 13 5
B addConstraint() 0 41 11
A error() 0 3 1
A truncate() 0 14 4
B tableExists() 0 23 8
C extendedReplace() 0 52 15
B modifyColumn() 0 28 9
A order() 0 20 5
C delete() 0 40 16
A decrypt() 0 13 3
A isConstraint() 0 9 2
B drop() 0 25 7
F fieldCheck() 0 53 20
C _escape() 0 32 16

How to fix   Complexity   

Complex Class

Complex classes like Database often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Database, and based on these observations, apply Extract Interface, too.

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
     * Constant defining parameters as a collection of objects
49
     *
50
     * @var int
51
     */
52
    private const COLLECTION = 1;
53
54
    /**
55
     * Constant defining parameters as an array
56
     *
57
     * @var int
58
     */
59
    private const ARRAY = 2;
60
61
    /**
62
     * Constant defining parameters as an objects
63
     *
64
     * @var int
65
     */
66
    private const OBJECT = 3;
67
68
    /**
69
     * Constant defining parameters as a string
70
     *
71
     * @var int
72
     */
73
    private const STRING = 4;
74
75
    /**
76
     * Constant defining parameters as an array of objects
77
     */
78
    private const ARRAY_OBJECT = 5;
79
80
    /**
81
     * Constant defining a SELECT query
82
     *
83
     * @var integer
84
     */
85
    private const SELECT = 1;
86
87
    /**
88
     * Constant defining a SELECT COUNT query
89
     *
90
     * @var integer
91
     */
92
    private const SELECT_COUNT = 2;
93
94
    /**
95
     * Constant defining a CREATE TABLE query
96
     *
97
     * @var integer
98
     */
99
    private const CREATE_TABLE = 3;
100
101
    /**
102
     * Constant defining DROP query
103
     *
104
     * @var integer
105
     */
106
    private const DROP = 4;
107
108
    /**
109
     * Constant defining DELETE query
110
     *
111
     * @var integer
112
     */
113
    private const DELETE = 5;
114
115
    /**
116
     * Constant defining INSERT query
117
     *
118
     * @var integer
119
     */
120
    private const INSERT = 6;
121
122
    /**
123
     * Constant defining REPLACE query
124
     *
125
     * @var integer
126
     */
127
    private const REPLACE = 7;
128
129
    /**
130
     * Constant defining UPDATE query
131
     *
132
     * @var integer
133
     */
134
    private const UPDATE = 8;
135
136
    /**
137
     * Constant defining EXTENDED INSERT query
138
     *
139
     * @var integer
140
     */
141
    private const EXTENDED_INSERT = 9;
142
143
    /**
144
     * Constant defining EXTENDED REPLACE query
145
     *
146
     * @var integer
147
     */
148
    private const EXTENDED_REPLACE = 10;
149
150
    /**
151
     * Constant defining EXTENDED UPDATE query
152
     *
153
     * @var integer
154
     */
155
    private const EXTENDED_UPDATE = 11;
156
157
    /**
158
     * Constant defining ALTER TABLE query
159
     *
160
     * @var integer
161
     */
162
    private const ALTER_TABLE = 12;
163
164
    /**
165
     * Constant defining action for alter table statement
166
     *
167
     * @var integer
168
     */
169
    private const ADD_COLUMN = 1;
170
171
    /**
172
     * Constant defining action for alter table statement
173
     *
174
     * @var integer
175
     */
176
    private const DROP_COLUMN = 2;
177
178
    /**
179
     * Constant defining action for alter table statement
180
     *
181
     * @var integer
182
     */
183
    private const MODIFY_COLUMN = 3;
184
185
    /**
186
     * Constant defining action to add a constraint
187
     *
188
     * @var integer
189
     */
190
    private const ADD_CONSTRAINT = 4;
191
192
    /**
193
     * Constant defining a TRUNCATE TABLE query
194
     *
195
     * @var integer
196
     */
197
    private const TRUNCATE = 13;
198
199
    /**
200
     * The mysqli connection
201
     *
202
     * @access protected
203
     * @var mysqli
204
     */
205
    protected $_c;
206
207
    /**
208
     * To store the SQL statement
209
     *
210
     * @access private
211
     * @var string
212
     */
213
    private $_sql = '';
214
215
    /**
216
     * A variable to store the type of query that is being run
217
     *
218
     * @access private
219
     * @var int
220
     */
221
    private $_queryType = null;
222
223
    /**
224
     * The result of the query
225
     *
226
     * @access protected
227
     * @var mixed
228
     */
229
    protected $_result = null;
230
231
    /**
232
     * Log level
233
     *
234
     * @access private
235
     * @var int
236
     */
237
    private $_logLevel = Logger::ERROR;
238
239
    /**
240
     * Variable to store the logger
241
     *
242
     * @access private
243
     * @var \Monolog\Logger
244
     */
245
    private $_logger = null;
246
247
    /**
248
     * Path for the logger to log the file
249
     *
250
     * @access private
251
     * @var string
252
     */
253
    private $_logPath = null;
254
255
    /**
256
     * Variable to store the most recent insert ID from an insert query
257
     *
258
     * @access protected
259
     * @var mixed
260
     */
261
    protected $_insertId = null;
262
263
    /**
264
     * Constructor
265
     *
266
     * @param string $strLogPath
267
     *            [optional] absolute log path for the log file
268
     * @param mysqli $dbh
269
     *            [optional]
270
     *            [by ref]
271
     *            mysqli object to perform queries.
272
     * @param int $intLogLevel
273
     *            [optional] Level of the log file to output
274
     *
275
     * @uses PHP_DB_ENCRYPT bool to define if the password is encrypted
276
     * @uses PHP_DB_PWD string to store the password
277
     * @uses PHP_DB_SERVER string to store the database server name or IP
278
     * @uses PHP_DB_USER string to store the name of the user used to connect to the server
279
     * @uses PHP_DB_SCHEMA string to store the default schema to connect to
280
     * @uses PHP_DB_LOG_LEVEL int to store the PSR-4 log level for the library
281
     * @uses PHP_DB_CLI_LOG bool to store if logs should be echoed to STDOUT
282
     */
283 132
    public function __construct($strLogPath = __DIR__, mysqli &$dbh = null, $intLogLevel = null)
284
    {
285
        // set the log file path
286 132
        $this->_logPath = $strLogPath;
287 132
        if (!file_exists($this->_logPath)) {
288
            touch($this->_logPath . "/db.log");
289
        }
290
291
        // set the log level
292 132
        if (!defined("PHP_DB_LOG_LEVEL") && is_null($intLogLevel)) {
293
            $this->_logLevel = Logger::ERROR;
294 132
        } elseif (!is_null($intLogLevel)) {
295
            $this->_logLevel = $intLogLevel;
296 132
        } elseif (defined('PHP_DB_LOG_LEVEL')) {
297 132
            $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...
298
        }
299
300
        // open the log handler
301 132
        $this->_logger = new Logger('db', [
302 132
            new StreamHandler(realpath($this->_logPath . "/db.log"), $this->_logLevel)
303
        ]);
304
305
        // check to see if we are operating in a CLI and if the user wants log data output to the terminal
306 132
        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...
307
            $stream = new StreamHandler(STDOUT, $this->_logLevel);
308
            $stream->setFormatter(new LineFormatter("%datetime% %level_name% %message%" . PHP_EOL, "H:i:s.u"));
309
            $this->_logger->pushHandler($stream);
310
        }
311
312
        // check to see if a connection was passed and all defined constants are present to establish a connection
313 132
        if (! is_null($dbh) && is_a($dbh, 'mysqli')) {
314 1
            $this->_logger->debug("Connecting through existing connection");
315 1
            $this->_c = $dbh;
316 132
        } elseif (!defined('PHP_DB_SERVER') || !defined('PHP_DB_USER') || !defined('PHP_DB_PWD') || !defined('PHP_DB_SCHEMA')) {
317
            $this->_logger->critical("Missing essential defined constants");
318
            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);
319 132
        } elseif (defined('PHP_DB_ENCRYPT') && (!defined('PHP_DB_ENCRYPT_ALGORITHM') || !defined('PHP_DB_ENCRYPT_SALT'))) {
320
            $this->_logger->critical("Missing essential encryption constants");
321
            throw new MissingOrInvalidParam("Missing required PHP_DB_ENCRYPT_ALGORITHM or PHP_DB_ENCRYPT_SALT constants");
322
        }
323
324
        // check to see if the password is encrypted and decrypt if it is
325 132
        if (defined('PHP_DB_ENCRYPT') && PHP_DB_ENCRYPT) {
326 132
            $this->_logger->debug("Decrypting password");
327 132
            $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...
328
        } else {
329
            $pwd = PHP_DB_PWD;
330
        }
331
332
        // open the connection
333 132
        $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_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_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_USER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
334
335
        // check for a connection error and throw an error if there is one
336 132
        if ($this->_c->connect_errno) {
337
            $this->_logger->error("Error connecting to database {$this->_c->connect_error}");
338
            throw new ConnectError("Could not create database class due to error {$this->_c->connect_error}", E_ERROR);
339
        }
340
341
        // update the logger with the connection details
342 132
        $this->_logger->info("Database connected");
343 132
        $this->_logger->debug("Connection details:", [
344 132
            'Server' => PHP_DB_SERVER,
345 132
            'User'   => PHP_DB_USER,
346 132
            'Schema' => PHP_DB_SCHEMA
347
        ]);
348
349 132
        $this->setVar("time_zone", "+00:00");
350 132
        $this->setVar("sql_mode", "");
351 132
    }
352
353
    /**
354
     * Function to make sure that the database is connected
355
     *
356
     * @return bool
357
     *
358
     * @throws ConnectError
359
     */
360 17
    public function isConnected(): bool
361
    {
362 17
        $this->_logger->info("Checking for live connection");
363
364 17
        if (is_a($this->_c, 'mysqli')) {
365 16
            $ret = $this->_c->ping();
366
        } else {
367 1
            throw new ConnectError("Connection lost");
368
        }
369
370 16
        return $ret;
371
    }
372
373
    /**
374
     * Setter function for _logger
375
     *
376
     * @param Logger $log
377
     *
378
     * @return bool
379
     */
380 1
    public function setLogger(Logger $log): bool
381
    {
382
        // set the logger
383 1
        $this->_logger->debug("Setting logger");
384 1
        $this->_logger = $log;
385 1
        return true;
386
    }
387
388
    /**
389
     * Getter function for _logLevel
390
     *
391
     * @return int
392
     */
393 1
    public function getLogLevel(): int
394
    {
395 1
        $level = $this->_logLevel;
396
397 1
        $this->_logger->debug("Getting log level ({$level})");
398 1
        return $level;
399
    }
400
401
    /**
402
     * Getter function for _queryType
403
     *
404
     * @return int
405
     */
406 1
    public function getQueryType(): int
407
    {
408 1
        $this->_logger->debug("Getting query type");
409 1
        return $this->_queryType;
410
    }
411
412
    /**
413
     * Setter function for _queryType
414
     *
415
     * @param int $qt
416
     *      Class constants that store query type
417
     *
418
     * @return Database
419
     */
420 102
    private function setQueryType(int $qt): Database
421
    {
422 102
        $this->_logger->debug("Setting query type");
423 102
        $this->_queryType = $qt;
424
425 102
        return $this;
426
    }
427
428
    /**
429
     * Magic method to convert the class to a string represented by the SQL query
430
     *
431
     * @return string|null
432
     */
433 68
    public function __toString(): string
434
    {
435 68
        $this->_logger->notice("__toString");
436 68
        $this->_logger->debug($this->_sql);
437 68
        return $this->_sql;
438
    }
439
440
    /**
441
     * Function to return the currently selected database schema
442
     *
443
     * @return string|bool
444
     *      Returns the string name of the selected schema or FALSE if none selected
445
     */
446 1
    public function getSchema()
447
    {
448 1
        if ($res = $this->_c->query("SELECT DATABASE()")) {
449 1
            $row = $res->fetch_row();
450
451 1
            $this->_logger->debug("Getting schema {$row[0]}");
452 1
            return $row[0];
453
        }
454
455
        return false;
456
    }
457
458
    /**
459
     * Function to set schema
460
     *
461
     * @param string $strSchema
462
     *
463
     * @return bool
464
     *
465
     * @throws QueryError
466
     */
467 2
    public function setSchema(string $strSchema): bool
468
    {
469 2
        $this->_logger->info("Setting schema to {$strSchema}");
470 2
        if (! $this->_c->select_db($strSchema)) {
471 1
            $this->_logger->error("Unknown schema {$strSchema}");
472 1
            throw new QueryError("Unknown database schema $strSchema");
473
        }
474 1
        return true;
475
    }
476
477
    /**
478
     * Method to set a MYSQL variable
479
     *
480
     * @param string $strName
481
     *      Name of the SQL variable to set
482
     * @param string $strVal
483
     *      Value for the variable
484
     *
485
     * @return bool
486
     */
487 132
    public function setVar(string $strName, ?string $strVal): bool
488
    {
489 132
        if (empty($strName)) {
490 1
            $this->_logger->debug("name is blank", [
491 1
                'name'  => $strName
492
            ]);
493 1
            return false;
494
        }
495
496 132
        $this->_logger->debug("Setting {$strName} = '{$strVal}'");
497
498 132
        if ($this->_c->real_query("SET $strName = {$this->_escape($strVal)}")) {
499 132
            $this->_logger->info("Var set");
500 132
            return true;
501
        } else {
502 1
            $this->_logger->error("Failed to set variable {$this->_c->error}");
503 1
            return false;
504
        }
505
    }
506
507
    /**
508
     * Function to execute the statement
509
     *
510
     * @param int $return
511
     *            [optional]
512
     *            MYSQLI constant to control what is returned from the mysqli_result object
513
     * @param string $strSql
514
     *            [optional]
515
     *            Optional SQL query
516
     *
517
     * @throws QueryError
518
     * @throws ConnectError
519
     *
520
     * @return mixed
521
     */
522 15
    public function execute(int $return = MYSQLI_OBJECT, ?string $strSql = null)
523
    {
524 15
        if (! is_null($strSql)) {
525 2
            $this->_sql = $strSql;
526
        }
527
528 15
        $this->_result = false;
529 15
        $this->_insertId = null;
530
531 15
        $this->isConnected();
532
533 15
        $this->_logger->info("Executing {$this->_queryType} query");
534 15
        $this->_logger->debug($this->_sql);
535
536
        try {
537 15
            if (in_array($this->_queryType, [
538 15
                self::SELECT,
539 15
                self::SELECT_COUNT
540
            ])) {
541 6
                $this->_result = $this->_c->query($this->_sql);
542 6
                if ($this->_c->error) {
543
                    $this->_logger->error("There is an error {$this->_c->error}");
544
                    $this->_logger->debug("Errored on query", [$this->_sql]);
545
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
546
                }
547
            } else {
548 9
                $this->_result = $this->_c->real_query($this->_sql);
549 9
                if ($this->_c->errno) {
550 1
                    $this->_logger->error("There was an error {$this->_c->error}");
551 1
                    $this->_logger->debug("Errored on query", [$this->_sql]);
552 1
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
553
                }
554
            }
555
556 14
            $this->_logger->debug("Checking for query results");
557 14
            $this->_result = $this->checkResults($return);
558 1
        } catch (QueryError $e) {
559 1
            $this->_logger->error($e);
560
        }
561
562 15
        return $this->_result;
563
    }
564
565
    /**
566
     * Function to check the results and return what is expected
567
     *
568
     * @param mixed $returnType
569
     *            [optional]
570
     *            Optional return mysqli_result return type
571
     *
572
     * @return mixed
573
     */
574 14
    protected function checkResults(int $returnType)
575
    {
576 14
        $res = null;
577
578
        // check the sql results and process appropriately
579 14
        if (in_array($this->_queryType, [Database::CREATE_TABLE, Database::ALTER_TABLE, Database::TRUNCATE, Database::DROP])) {
580 5
            $res = $this->_result;
581 9
        } elseif (in_array($this->_queryType, [Database::INSERT, Database::EXTENDED_INSERT, Database::DELETE, Database::UPDATE, Database::EXTENDED_UPDATE, Database::REPLACE, Database::EXTENDED_REPLACE, Database::DELETE])) {
582 2
            $res = $this->_c->affected_rows;
583
584 2
            if (in_array($this->_queryType, [Database::INSERT, Database::REPLACE, Database::EXTENDED_INSERT])) {
585 2
                $this->_insertId = $this->_c->insert_id;
586
            }
587 7
        } elseif ($this->_queryType == Database::SELECT_COUNT) {
588 1
            if (! is_a($this->_result, 'mysqli_result')) {
589
                $this->_logger->error("Error with return on query");
590
                return null;
591
            }
592
593 1
            if ($this->_result->num_rows == 1) {
594 1
                $row = $this->_result->fetch_assoc();
595 1
                if (isset($row['count'])) {
596 1
                    $this->_logger->debug("Returning SELECT_COUNT query", [
597 1
                        'count' => $row['count']
598
                    ]);
599 1
                    $res = $row['count'];
600
                }
601
            } elseif ($this->_result->num_rows > 1) {
602
                $this->_logger->debug("Returning SELECT_COUNT query", [
603
                    'count' => $this->_result->num_rows
604
                ]);
605
                $res = $this->_result->num_rows;
606
            }
607
608 1
            mysqli_free_result($this->_result);
609
        } else {
610 6
            $method = "mysqli_fetch_object";
611 6
            if ($returnType == MYSQLI_ASSOC) {
612 1
                $method = "mysqli_fetch_assoc";
613 5
            } elseif ($returnType == MYSQLI_NUM) {
614 1
                $method = "mysqli_fetch_array";
615
            }
616
617 6
            if (is_a($this->_result, 'mysqli_result')) {
618 5
                if ($this->_result->num_rows > 1) {
619 3
                    $res = [];
620 3
                    while ($row = call_user_func($method, $this->_result)) {
621 3
                        $res[] = $row;
622
                    }
623
                } else {
624 2
                    $res = call_user_func($method, $this->_result);
625
                }
626
            } else {
627 1
                $this->_logger->error("Error with return on query");
628 1
                return null;
629
            }
630
        }
631
632 13
        if ($this->_c->error) {
633
            $this->_logger->error("Encountered a SQL error", ['error' => $this->_c->error, 'list' => $this->_c->error_list]);
634
            $this->_logger->debug("Debug", ['debug' => debug_backtrace()]);
635
            return null;
636
        }
637
638 13
        return $res;
639
    }
640
641
    /**
642
     * Function to pass through calling the query function (used for backwards compatibility and for more complex queries that aren't currently supported)
643
     * Nothing is escaped
644
     *
645
     * @param string $strSql
646
     *            [optional]
647
     *            Optional query to pass in and execute
648
     *
649
     * @return \mysqli_result|bool
650
     */
651 2
    public function query(?string $strSql = null)
652
    {
653 2
        return is_null($strSql) ? $this->_c->query($this->_sql) : $this->_c->query($strSql);
654
    }
655
656
    /**
657
     * A function to build a select query
658
     *
659
     * @param string $strTableName
660
     *            The table to query
661
     * @param array|string $fields
662
     *            [optional]
663
     *            Optional array of fields to return (defaults to '*')
664
     * @param array:DBWhere|DBWhere $arrWhere
665
     *            [optional] Where clause data
666
     * @param array $arrFlags
667
     *            [optional]
668
     *            Optional 2-dimensional array to allow other flags
669
     *
670
     * @see Database::flags()
671
     *
672
     * @throws Exception
673
     * @throws InvalidArgumentException
674
     *
675
     * @return mixed
676
     *
677
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
678
     */
679 37
    public function select(string $strTableName, $fields = null, $arrWhere = [], ?array $arrFlags = [])
680
    {
681 37
        $this->_sql = null;
682 37
        $this->setQueryType(self::SELECT);
683
684
        // starting building the query
685 37
        if ($this->checkTableName($strTableName)) {
686 37
            $this->_logger->debug("Starting SELECT query of {$strTableName}", [
687 37
                'fields' => $this->fields($fields)
688
            ]);
689 36
            $this->_sql = "SELECT " . $this->fields($fields) . " FROM $strTableName";
690
        }
691
692
        // add in any joins
693 36
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins']) && count($arrFlags['joins'])) {
694 1
            $this->_logger->debug("Adding joins", [
695 1
                'joins' => implode(' ', $arrFlags['joins'])
696
            ]);
697 1
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
698
        } else {
699 35
            $this->_logger->debug("No joins");
700
        }
701
702
        // parse the where clauses
703 36
        $where = $this->parseClause($arrWhere);
704
705 34
        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...
706 8
            $where_str = " WHERE";
707 8
            $this->_logger->debug("Parsing where clause and adding to query");
708 8
            foreach ($where as $x => $w) {
709 8
                if ($x > 0) {
710 1
                    $where_str .= " {$w->sqlOperator}";
711
                }
712 8
                $where_str .= $w;
713
            }
714 8
            if (strlen($where_str) > strlen(" WHERE")) {
715 8
                $this->_sql .= $where_str;
716
            }
717
        }
718
719
        // search for any other flags (order, having, group)
720 34
        if (is_array($arrFlags) && count($arrFlags)) {
721 9
            $this->_logger->debug("Parsing flags and adding to query", $arrFlags);
722 9
            $this->_sql .= $this->flags($arrFlags);
723
        }
724
725 32
        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...
726
            return $this->execute();
727
        }
728
729 32
        return $this->_sql;
730
    }
731
732
    /**
733
     * Function to build a query to check the number of rows in a table
734
     *
735
     * @param string $strTableName
736
     *            The table to query
737
     * @param array:DBWhere|DBWhere $arrWhere
738
     *            [optional]
739
     *            Optional 2-dimensional array to build where clause
740
     * @param array $arrFlags
741
     *            [optional]
742
     *            Optional 2-dimensional array to add flags
743
     *
744
     * @see Database::flags()
745
     *
746
     * @return string|int|NULL
747
     *
748
     * @throws InvalidArgumentException
749
     *
750
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
751
     */
752 4
    public function selectCount(string $strTableName, $arrWhere = [], ?array $arrFlags = [])
753
    {
754 4
        $this->_sql = null;
755 4
        $this->setQueryType(self::SELECT_COUNT);
756
757
        // start building query
758 4
        if ($this->checkTableName($strTableName)) {
759 4
            $this->_sql = "SELECT COUNT(1) AS 'count' FROM $strTableName";
760
        }
761
762
        // add in any joins
763 4
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
764 1
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
765
        }
766
767
        // parse where clauses
768 4
        $where = $this->parseClause($arrWhere);
769
770 4
        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...
771 2
            $where_str = " WHERE";
772 2
            $this->_logger->debug("Parsing where clause and adding to query");
773 2
            foreach ($where as $x => $w) {
774 2
                if ($x > 0) {
775 1
                    $where_str .= " {$w->sqlOperator}";
776
                }
777 2
                $where_str .= $w;
778
            }
779 2
            if (strlen($where_str) > strlen(" WHERE")) {
780 2
                $this->_sql .= $where_str;
781
            }
782
        }
783
784
        // add in additional flags (group, having, order)
785 4
        if (is_array($arrFlags) && count($arrFlags)) {
786 1
            $this->_sql .= $this->flags($arrFlags);
787
        }
788
789 4
        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...
790
            return $this->execute();
791
        }
792
793 4
        return $this->_sql;
794
    }
795
796
    /**
797
     * Function to build an insert query statement
798
     *
799
     * @param string $strTableName
800
     * @param mixed $arrParams
801
     * @param bool $blnToIgnore
802
     *
803
     * @return string|NULL
804
     *
805
     * @throws InvalidArgumentException
806
     * @throws MissingInterfaceAndMethods
807
     *
808
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
809
     */
810 7
    public function insert(string $strTableName, $params, bool $blnToIgnore = false)
811
    {
812 7
        $this->_sql = null;
813 7
        $this->setQueryType(self::INSERT);
814
815
        // start building query
816 7
        if ($this->checkTableName($strTableName)) {
817 7
            $this->_sql = "INSERT" . ($blnToIgnore ? " IGNORE" : "") . " INTO {$strTableName}";
818
        }
819
820 7
        $paramType = $this->checkParamType($params);
821
822
        // add in field parameters and values
823 6
        if ($paramType == self::ARRAY) {
824 3
            $keys = array_keys($params);
825 3
            $vals = array_values($params);
826 3
            $this->_sql .= " (`" . implode("`,`", $keys) . "`)";
827 3
            $this->_sql .= " VALUES (" . implode(",", array_map([
828 3
                $this,
829 3
                '_escape'
830 3
            ], $vals)) . ")";
831 3
        } elseif ($paramType == self::STRING) {
832 1
            $this->_sql .= " {$params}";
833 2
        } elseif ($paramType == self::OBJECT) {
834 1
            $arr = $params->insert();
835 1
            $keys = array_keys($arr);
836 1
            $vals = array_values($arr);
837 1
            $this->_sql .= " (`" . implode("`,`", $keys) . "`) VALUES ";
838 1
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], $vals)) . ")";
839
        } else {
840 1
            throw new InvalidArgumentException("Invalid type passed to insert " . gettype($params));
841
        }
842
843 5
        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...
844
            return $this->execute();
845
        }
846
847 5
        return $this->_sql;
848
    }
849
850
    /**
851
     * Function to create an extended insert query statement
852
     *
853
     * @param string $strTableName
854
     *            The table name that the data is going to be inserted on
855
     * @param array $arrFields
856
     *            An array of field names that each value represents
857
     * @param mixed $params
858
     *            An array of array of values or a string with a SELECT statement to populate the insert with
859
     * @param bool $blnToIgnore
860
     *            [optional]
861
     *            Boolean to decide if we need to use the INSERT IGNORE INTO syntax
862
     *
863
     * @return NULL|string Returns the SQL if self::$autorun is set to false, else it returns the output from running.
864
     *
865
     * @throws InvalidArgumentException
866
     * @throws MissingOrInvalidParam
867
     * @throws MissingInterfaceAndMethods
868
     *
869
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
870
     */
871 8
    public function extendedInsert(string $strTableName, array $arrFields, $params, bool $blnToIgnore = false)
872
    {
873 8
        $this->_sql = null;
874 8
        $this->setQueryType(self::EXTENDED_INSERT);
875
876
        // start building query
877 8
        if ($this->checkTableName($strTableName)) {
878 8
            $this->_sql = "INSERT ".
879 8
                ($blnToIgnore ? "IGNORE " : "").
880 8
                "INTO $strTableName ".
881 8
                "(`".implode("`,`", $arrFields)."`)";
882
        }
883
884 8
        $paramType = $this->checkParamType($params);
885 6
        $this->_sql .= " VALUES ";
886
887 6
        if ($paramType == self::COLLECTION || $paramType == self::ARRAY_OBJECT) {
888 2
            foreach ($params as $p) {
889 2
                $key_value = $p->insert();
890 2
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
891
            }
892
893 2
            $this->_sql = substr($this->_sql, 0, -1);
894 4
        } elseif ($paramType == self::OBJECT) {
895
            $key_value = $params->insert();
896
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
897 4
        } elseif ($paramType == self::ARRAY) {
898 4
            foreach ($params as $p) {
899 4
                if (count($p) != count($arrFields)) {
900 2
                    $this->_logger->emergency("Inconsistent number of fields to values in extendedInsert", [
901 2
                        $p,
902 2
                        debug_backtrace()
903
                    ]);
904 2
                    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

904
                    throw new MissingOrInvalidParam("Inconsistent number of fields in fields and values in extendedInsert " . /** @scrutinizer ignore-type */ print_r($p, true));
Loading history...
905
                }
906 3
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($p))) . ")";
907
908 3
                if ($p != end($params)) {
909 3
                    $this->_sql .= ",";
910
                }
911
            }
912
        } else {
913
            throw new InvalidArgumentException("Invalid param type ".gettype($params));
914
        }
915
916 4
        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...
917
            return $this->execute();
918
        }
919
920 4
        return $this->_sql;
921
    }
922
923
    /**
924
     * Build a statement to update a table
925
     *
926
     * @param string $strTableName
927
     *            The table name to update
928
     * @param mixed $arrParams
929
     *            Name/value pairs of the field name and value
930
     * @param array:DBWhere|DBWhere $arrWhere
931
     *            [optional]
932
     *            DBWhere clauses
933
     * @param array $arrFlags
934
     *            [optional]
935
     *            Two-dimensional array to create other flag options (joins, order, and group)
936
     *
937
     * @see Database::flags()
938
     *
939
     * @return NULL|string
940
     *
941
     * @throws InvalidArgumentException
942
     * @throws MissingOrInvalidParam
943
     * @throws MissingInterfaceAndMethods
944
     *
945
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
946
     */
947 9
    public function update(string $strTableName, $params, $arrWhere = [], ?array $arrFlags = [])
948
    {
949 9
        $this->_sql = "UPDATE ";
950 9
        $this->setQueryType(self::UPDATE);
951
952 9
        if ($this->checkTableName($strTableName)) {
953 9
            $this->_sql .= $strTableName;
954
955 9
            if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
956 1
                $this->_sql .= " " . implode(" ", $arrFlags['joins']);
957 1
                unset($arrFlags['joins']);
958
            }
959
960 9
            $this->_sql .= " SET ";
961
        }
962
963 9
        $paramType = $this->checkParamType($params);
964
965 7
        if ($paramType == self::ARRAY) {
966 6
            $keys = array_keys($params);
967 6
            foreach ($params as $f => $p) {
968 6
                $field = $f;
969 6
                if ((strpos($f, "`") === false) &&
970 6
                    (strpos($f, ".") === false) &&
971 5
                    (strpos($f, "*") === false) &&
972 5
                    (stripos($f, " as ") === false)
973
                ) {
974 5
                    $field = "`{$f}`";
975
                }
976
977 6
                if (! is_null($p)) {
978 5
                    $this->_sql .= "$field={$this->_escape($p)}";
979
                } else {
980 1
                    $this->_sql .= "$field=NULL";
981
                }
982
983 6
                if ($f != end($keys)) {
984 1
                    $this->_sql .= ",";
985
                }
986
            }
987 1
        } elseif ($paramType == self::OBJECT) {
988 1
            $key_value = $params->update();
989 1
            $fields = array_keys($key_value);
990 1
            $values = array_map([$this, '_escape'], array_values($key_value));
991 1
            foreach ($fields as $x => $f) {
992 1
                if ($x > 0) {
993 1
                    $this->_sql .= ",";
994
                }
995 1
                $this->_sql .= "`{$f}`={$values[$x]}";
996
            }
997
        } else {
998
            throw new MissingOrInvalidParam("No fields to update");
999
        }
1000
1001 7
        $where = $this->parseClause($arrWhere);
1002
1003 7
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_null($where) is always false.
Loading history...
introduced by
The condition is_array($where) is always true.
Loading history...
1004 3
            $where_str = " WHERE";
1005 3
            $this->_logger->debug("Parsing where clause and adding to query");
1006 3
            foreach ($where as $x => $w) {
1007 3
                if ($x > 0) {
1008 1
                    $where_str .= " {$w->sqlOperator}";
1009
                }
1010 3
                $where_str .= $w;
1011
            }
1012 3
            if (strlen($where_str) > strlen(" WHERE")) {
1013 3
                $this->_sql .= $where_str;
1014
            }
1015
        }
1016
1017 7
        if (! is_null($arrFlags) && is_array($arrFlags) && count($arrFlags)) {
1018
            $this->_sql .= $this->flags($arrFlags);
1019
        }
1020
1021 7
        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...
1022
            return $this->execute();
1023
        }
1024
1025 7
        return $this->_sql;
1026
    }
1027
1028
    /**
1029
     * Function to offer an extended updated functionality by using two different tables.
1030
     *
1031
     * @param string $strTableToUpdate
1032
     *            The table that you want to update (alias 'tbu' is automatically added)
1033
     * @param string $strOriginalTable
1034
     *            The table with the data you want to overwrite to_be_updated table (alias 'o' is automatically added)
1035
     * @param string $strLinkField
1036
     *            The common index value between them that will join the fields
1037
     * @param array|string $arrParams
1038
     *            If string only a single field is updated (tbu.$params = o.$params)
1039
     *            If array each element in the array is a field to be updated (tbu.$param = o.$param)
1040
     *
1041
     * @return mixed
1042
     *
1043
     * @throws InvalidArgumentException
1044
     *
1045
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1046
     */
1047 2
    public function extendedUpdate(string $strTableToUpdate, string $strOriginalTable, string $strLinkField, array $arrParams)
1048
    {
1049 2
        $this->_sql = "UPDATE ";
1050 2
        $this->setQueryType(self::EXTENDED_UPDATE);
1051
1052 2
        if (! is_null($strTableToUpdate) && ! is_null($strOriginalTable) && ! is_null($strLinkField)) {
0 ignored issues
show
introduced by
The condition is_null($strOriginalTable) is always false.
Loading history...
introduced by
The condition is_null($strLinkField) is always false.
Loading history...
introduced by
The condition is_null($strTableToUpdate) is always false.
Loading history...
1053 2
            $this->_sql .= "$strTableToUpdate tbu INNER JOIN $strOriginalTable o USING ($strLinkField) SET ";
1054
        }
1055
1056 2
        if (is_array($arrParams) && count($arrParams)) {
1057 2
            foreach ($arrParams as $param) {
1058 2
                if ($param != $strLinkField) {
1059 2
                    $this->_sql .= "tbu.`$param` = o.`$param`,";
1060
                }
1061
            }
1062 2
            $this->_sql = substr($this->_sql, 0, - 1);
1063
        } elseif (is_string($arrParams)) {
1064
            $this->_sql .= "tbu.`$arrParams` = o.`$arrParams`";
1065
        } else {
1066
            throw new InvalidArgumentException("Do not understand datatype " . gettype($arrParams), E_ERROR);
1067
        }
1068
1069 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...
1070
            return $this->execute();
1071
        }
1072
1073 2
        return $this->_sql;
1074
    }
1075
1076
    /**
1077
     * Function to build a replace query
1078
     *
1079
     * @param string $strTableName
1080
     *            The table to update
1081
     * @param mixed $arrParams
1082
     *            Name/value pair to insert
1083
     *
1084
     * @return NULL|string
1085
     *
1086
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1087
     */
1088 2
    public function replace(string $strTableName, $params)
1089
    {
1090 2
        $this->_sql = null;
1091 2
        $this->setQueryType(self::REPLACE);
1092
1093 2
        if ($this->checkTableName($strTableName)) {
1094 2
            $this->_sql = "REPLACE INTO $strTableName ";
1095
        }
1096
1097 2
        $paramType = $this->checkParamType($params);
1098
1099 2
        if ($paramType == self::ARRAY) {
1100 1
            $keys = array_keys($params);
1101 1
            $vals = array_values($params);
1102
1103 1
            $this->_sql .= "(`" . implode("`,`", $keys) . "`)";
1104 1
            $this->_sql .= " VALUES (" . implode(",", array_map([
1105 1
                $this,
1106 1
                '_escape'
1107 1
            ], array_values($vals))) . ")";
1108 1
        } elseif ($paramType == self::OBJECT) {
1109 1
            $key_value = $params->replace();
1110 1
            $this->_sql .= "(`" . implode("`,`", array_keys($key_value)) . "`) VALUES ";
1111 1
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . ")";
1112
        } else {
1113
            throw new MissingOrInvalidParam('Arrays or Objects that implement DBInterface are the only valid types for replace');
1114
        }
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 an extended replace statement
1125
     *
1126
     * @param string $strTableName
1127
     *            Table name to update
1128
     * @param array $arrFields
1129
     *            Array of fields
1130
     * @param mixed $arrParams
1131
     *            Two-dimensional array of values
1132
     *
1133
     * @return NULL|string
1134
     *
1135
     * @throws InvalidArgumentException
1136
     *
1137
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1138
     */
1139 2
    public function extendedReplace(string $strTableName, array $arrFields, $params)
1140
    {
1141 2
        $this->_sql = null;
1142 2
        $this->setQueryType(self::EXTENDED_REPLACE);
1143
1144 2
        if (! is_array($arrFields) || ! count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_array($arrFields) is always true.
Loading history...
1145
            throw new InvalidArgumentException("Error with the field type");
1146
        }
1147
1148 2
        if ($this->checkTableName($strTableName)) {
1149 2
            $this->_sql = "REPLACE INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
1150
        }
1151
1152 2
        $paramType = $this->checkParamType($params);
1153 2
        $this->_sql .= " VALUES ";
1154
1155 2
        if ($paramType == self::ARRAY) {
1156 1
            foreach ($params as $p) {
1157 1
                $this->_sql .= "(" . implode(",", array_map([
1158 1
                    $this,
1159 1
                    '_escape'
1160 1
                ], array_values($p))) . ")";
1161
1162 1
                if ($p != end($params)) {
1163 1
                    $this->_sql .= ",";
1164
                }
1165
            }
1166 1
        } elseif ($paramType == self::COLLECTION || $paramType == self::ARRAY_OBJECT) {
1167 1
            if ($paramType == self::COLLECTION) {
1168 1
                $last = $params->last();
1169
            } elseif ($paramType == self::ARRAY_OBJECT) {
1170
                $last = end($params);
1171
            }
1172
1173 1
            foreach ($params as $p) {
1174 1
                $key_value = $p->replace();
1175 1
                $this->_sql .= "(" . implode(",", array_map([
1176 1
                    $this,
1177 1
                    '_escape'
1178 1
                ], array_values($key_value))) . ")";
1179
1180 1
                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...
1181
                    $this->_sql .= ",";
1182
                }
1183
            }
1184
        }
1185
1186 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...
1187
            return $this->execute();
1188
        }
1189
1190 2
        return $this->_sql;
1191
    }
1192
1193
    /**
1194
     * Function to build a delete statement
1195
     *
1196
     * @param string $strTableName
1197
     *            Table name to act on
1198
     * @param array $arrFields
1199
     *            [optional]
1200
     *            Optional list of fields to delete (used when including multiple tables)
1201
     * @param array:DBWhere|DBWhere $arrWhere
1202
     *            [optional]
1203
     *            Optional where clauses to use
1204
     * @param array $arrJoins
1205
     *            [optional]
1206
     *            Optional 2-dimensional array to add other flags
1207
     *
1208
     * @see Database::flags()
1209
     *
1210
     * @return string|NULL
1211
     *
1212
     * @throws InvalidArgumentException
1213
     *
1214
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1215
     */
1216 4
    public function delete(string $strTableName, ?array $arrFields = [], $arrWhere = [], ?array $arrJoins = [])
1217
    {
1218 4
        $this->_sql = "DELETE";
1219 4
        $this->setQueryType(self::DELETE);
1220
1221 4
        $this->_logger->debug("Deleting table data");
1222
1223 4
        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...
1224 1
            $this->_sql .= " " . implode(",", $arrFields);
1225
        }
1226
1227 4
        if ($this->checkTableName($strTableName)) {
1228 4
            $this->_sql .= " FROM $strTableName";
1229
        }
1230
1231 4
        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...
1232 1
            $this->_sql .= " " . implode(" ", $arrJoins);
1233
        }
1234
1235 4
        $where = $this->parseClause($arrWhere);
1236
1237 4
        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...
1238 2
            $where_str = " WHERE";
1239 2
            $this->_logger->debug("Parsing where clause and adding to query");
1240 2
            foreach ($where as $x => $w) {
1241 2
                if ($x > 0) {
1242 1
                    $where_str .= " {$w->sqlOperator}";
1243
                }
1244 2
                $where_str .= $w;
1245
            }
1246 2
            if (strlen($where_str) > strlen(" WHERE")) {
1247 2
                $this->_sql .= $where_str;
1248
            }
1249
        }
1250
1251 4
        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...
1252
            return $this->execute();
1253
        }
1254
1255 4
        return $this->_sql;
1256
    }
1257
1258
    /**
1259
     * Function to build a drop table statement (automatically executes)
1260
     *
1261
     * @param string $strTableName
1262
     *            Table to drop
1263
     * @param string $strType
1264
     *            [optional]
1265
     *            Type of item to drop ('table', 'view') (defaulted to 'table')
1266
     * @param bool $blnIsTemp
1267
     *            [optional]
1268
     *            Optional bool if this is a temporary table
1269
     *
1270
     * @return string|NULL
1271
     *
1272
     * @throws InvalidArgumentException
1273
     *
1274
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1275
     */
1276 5
    public function drop(string $strTableName, string $strType = 'table', bool $blnIsTemp = false)
1277
    {
1278 5
        $this->_sql = null;
1279 5
        $this->setQueryType(self::DROP);
1280
1281 5
        switch ($strType) {
1282 5
            case 'table':
1283 4
                $strType = 'TABLE';
1284 4
                break;
1285 1
            case 'view':
1286 1
                $strType = 'VIEW';
1287 1
                break;
1288
            default:
1289
                throw new InvalidArgumentException("Invalid type " . gettype($strType), E_ERROR);
1290
        }
1291
1292 5
        if ($this->checkTableName($strTableName)) {
1293 5
            $this->_sql = "DROP" . ($blnIsTemp ? " TEMPORARY" : "") . " $strType IF EXISTS `{$strTableName}`";
1294
        }
1295
1296 5
        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...
1297
            return $this->execute();
1298
        }
1299
1300 5
        return $this->_sql;
1301
    }
1302
1303
    /**
1304
     * Function to build a truncate table statement (automatically executes)
1305
     *
1306
     * @param string $strTableName
1307
     *            Table to truncate
1308
     *
1309
     * @return string|NULL
1310
     *
1311
     * @throws InvalidArgumentException
1312
     *
1313
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1314
     */
1315 1
    public function truncate(string $strTableName)
1316
    {
1317 1
        $this->_sql = null;
1318 1
        $this->setQueryType(self::TRUNCATE);
1319
1320 1
        if ($this->checkTableName($strTableName)) {
1321 1
            $this->_sql = "TRUNCATE TABLE $strTableName";
1322
        }
1323
1324 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...
1325
            return $this->execute();
1326
        }
1327
1328 1
        return $this->_sql;
1329
    }
1330
1331
    /**
1332
     * Function to build a create temporary table statement
1333
     *
1334
     * @param string $strTableName
1335
     *            Name to give the table when creating
1336
     * @param bool $blnIsTemp
1337
     *            [optional]
1338
     *            Optional bool to make the table a temporary table
1339
     * @param mixed $strSelect
1340
     *            [optional]
1341
     *            Optional parameter if null uses last built statement
1342
     *            If string, will be made the SQL statement executed to create the table
1343
     *            If array, 2-dimensional array with "field", "datatype" values to build table fields
1344
     *
1345
     * @return NULL|string
1346
     *
1347
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1348
     */
1349 4
    public function createTable(string $strTableName, bool $blnIsTemp = false, $strSelect = null)
1350
    {
1351 4
        $this->setQueryType(self::CREATE_TABLE);
1352
1353 4
        if (is_null($strSelect) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1354 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($this->_sql)";
1355 3
        } elseif ($this->checkTableName($strTableName) && is_string($strSelect)) {
1356 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($strSelect)";
1357 2
        } elseif ($this->checkTableName($strTableName) && is_array($strSelect)) {
1358 2
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName (";
1359
1360 2
            foreach ($strSelect as $field) {
1361 2
                $default = null;
1362 2
                if (is_a($field, 'Godsgood33\Php_Db\DBCreateTable')) {
1363 2
                    $this->_sql .= (string) $field . ",";
1364
                } elseif (is_array($field)) {
1365
                    if (isset($field['default'])) {
1366
                        $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1367
                    }
1368
                    $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1369
                }
1370
            }
1371 2
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1372
        }
1373
1374 4
        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...
1375
            return $this->execute();
1376
        }
1377
1378 4
        return $this->_sql;
1379
    }
1380
1381
    /**
1382
     * Function to create a table using a stdClass object derived from JSON
1383
     *
1384
     * @param stdClass $json
1385
     * @param bool $blnDropFirst bool to decide if you want to drop the table first
1386
     *
1387
     * @example /examples/create_table_json.json
1388
     *
1389
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1390
     */
1391 3
    public function createTableJson($json, bool $blnDropFirst = false)
1392
    {
1393 3
        $this->setQueryType(self::CREATE_TABLE);
1394 3
        $this->_c->select_db($json->schema);
1395
1396 3
        if ($blnDropFirst) {
1397
            $this->drop($json->name);
1398
            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...
1399
                $this->execute();
1400
            }
1401
        }
1402
1403 3
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1404 3
        foreach ($json->fields as $field) {
1405 3
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1406
1407 3
            if ($field->dataType == 'enum' && isset($field->values)) {
1408 1
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1409
            }
1410
1411 3
            if (isset($field->ai) && $field->ai) {
1412 3
                $this->_sql .= " AUTO_INCREMENT";
1413
            }
1414
1415 3
            if (isset($field->nn) && $field->nn) {
1416 3
                $this->_sql .= " NOT NULL";
1417 2
            } elseif (isset($field->default)) {
1418 2
                if (strtolower($field->default) == 'null') {
1419 1
                    $this->_sql .= " DEFAULT NULL";
1420 1
                } elseif (strlen($field->default)) {
1421 1
                    $this->_sql .= " DEFAULT '{$field->default}'";
1422
                }
1423
            }
1424
1425 3
            if ($field != end($json->fields)) {
1426 2
                $this->_sql .= ",";
1427
            }
1428
        }
1429
1430 3
        if (isset($json->index) && count($json->index)) {
1431 1
            foreach ($json->index as $ind) {
1432 1
                $ref = null;
1433 1
                if (is_array($ind->ref)) {
1434
                    $ref = "";
1435
                    foreach ($ind->ref as $r) {
1436
                        $ref .= "`{$r}` ASC,";
1437
                    }
1438
                    $ref = substr($ref, 0, -1);
1439 1
                } elseif (is_string($ind->ref)) {
1440 1
                    $ref = $ind->ref;
1441
                }
1442 1
                if (!is_null($ref)) {
1443 1
                    $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ref}`)";
1444
                }
1445
            }
1446
        }
1447
1448 3
        if (isset($json->constraints) && count($json->constraints)) {
1449
            foreach ($json->constraints as $con) {
1450
                $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));
1451
            }
1452
        }
1453
1454 3
        if (isset($json->unique) && count($json->unique)) {
1455 1
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1456
        }
1457
1458 3
        if (isset($json->primary_key) && count($json->primary_key)) {
1459 3
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1460
        } else {
1461
            if (substr($this->_sql, - 1) == ',') {
1462
                $this->_sql = substr($this->_sql, 0, - 1);
1463
            }
1464
1465
            $this->_sql .= ")";
1466
        }
1467
1468 3
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
1469
            return $this->execute();
1470
        }
1471
1472 3
        return $this->_sql;
1473
    }
1474
1475
    /**
1476
     * Method to add a column to the database (only one at a time!)
1477
     *
1478
     * @param string $strTableName
1479
     * @param stdClass $params
1480
     *
1481
     * @return string|mixed
1482
     *
1483
     * @throws InvalidArgumentException
1484
     *
1485
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1486
     */
1487 3
    public function addColumn(string $strTableName, stdClass $params)
1488
    {
1489 3
        $this->setQueryType(self::ALTER_TABLE);
1490 3
        $this->checkTableName($strTableName);
1491
1492 3
        $this->_sql = "ALTER TABLE {$strTableName} ADD COLUMN";
1493
1494 3
        if (!self::checkObject($params, ['name', 'dataType'])) {
1495 1
            $this->_logger->error("Missing elements for the addColumn method (need 'name', 'dataType')", [$params]);
1496 1
            throw new InvalidArgumentException("Missing elements for the addColumn method");
1497
        }
1498
1499 2
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1500 2
        $default = null;
1501 2
        if ($params->default === null) {
1502 1
            $default = " DEFAULT NULL";
1503 1
        } elseif (strlen($params->default)) {
1504 1
            $default = " DEFAULT {$this->_escape($params->default)}";
1505
        }
1506 2
        $this->_sql .= " `{$params->name}` {$params->dataType}" . $nn . $default;
1507
1508 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...
1509
            return $this->execute();
1510
        }
1511
1512 2
        return $this->_sql;
1513
    }
1514
1515
    /**
1516
     * Method to drop a fields from a table
1517
     *
1518
     * @param string $strTableName
1519
     * @param string|array:string $params
1520
     *
1521
     * @return string|mixed
1522
     *
1523
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1524
     */
1525 3
    public function dropColumn(string $strTableName, $params)
1526
    {
1527 3
        $this->setQueryType(self::ALTER_TABLE);
1528 3
        $this->_sql = "ALTER TABLE {$strTableName} DROP COLUMN";
1529
1530 3
        if (is_array($params) && count($params)) {
1531 2
            foreach ($params as $col) {
1532 2
                $this->_sql .= " `{$col->name}`";
1533
1534 2
                if ($col != end($params)) {
1535 1
                    $this->_sql .= ",";
1536
                }
1537
            }
1538 1
        } elseif (is_string($params)) {
1539 1
            $this->_sql .= " `{$params}`";
1540
        }
1541
1542 3
        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...
1543
            return $this->execute();
1544
        }
1545
1546 3
        return $this->_sql;
1547
    }
1548
1549
    /**
1550
     * Method to modify a field to change it's datatype, name, or other parameter
1551
     *
1552
     * @param string $strTableName
1553
     * @param stdClass $params
1554
     *
1555
     * @return string|mixed
1556
     *
1557
     * @throws InvalidArgumentException
1558
     *
1559
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1560
     */
1561 3
    public function modifyColumn($strTableName, $params)
1562
    {
1563 3
        $this->setQueryType(self::ALTER_TABLE);
1564 3
        $this->_sql = "ALTER TABLE {$strTableName} MODIFY COLUMN";
1565
1566 3
        if (!self::checkObject($params, ['name', 'dataType'])) {
1567 1
            $this->_logger->error("Missing elements to the modifyColumn method (need 'name' and 'dataType')", [$params]);
1568 1
            throw new InvalidArgumentException("Missing elements to the modifyColumn method");
1569
        }
1570
1571 2
        if (!isset($params->new_name)) {
1572 1
            $params->new_name = $params->name;
1573
        }
1574
1575 2
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1576 2
        $default = null;
1577 2
        if ($params->default === null) {
1578 1
            $default = " DEFAULT NULL";
1579 1
        } elseif (strlen($params->default)) {
1580 1
            $default = " DEFAULT {$this->_escape($params->default)}";
1581
        }
1582 2
        $this->_sql .= " `{$params->name}` `{$params->new_name}` {$params->dataType}" . $nn . $default;
1583
1584 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...
1585
            return $this->execute();
1586
        }
1587
1588 2
        return $this->_sql;
1589
    }
1590
1591
    /**
1592
     * Method to add a constraint to a table
1593
     *
1594
     * @param string $strTableName
1595
     * @param stdClass $params
1596
     *
1597
     * @return string|mixed
1598
     *
1599
     * @throws InvalidArgumentException
1600
     *
1601
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1602
     */
1603 7
    public function addConstraint($strTableName, $params)
1604
    {
1605 7
        $this->setQueryType(self::ALTER_TABLE);
1606 7
        $this->_sql = "ALTER TABLE {$strTableName} ADD CONSTRAINT";
1607
1608 7
        if (!is_a($params, 'stdClass')) {
1609 1
            $this->_logger->critical("Error in reading constraint field");
1610 1
            throw new InvalidArgumentException("Error in reading constraint field");
1611
        }
1612
1613 6
        if (!self::checkObject($params, ['id', 'local', 'schema', 'table', 'field', 'delete', 'update'])) {
1614 1
            $this->_logger->error("Missing elements in the addConstraint method (need 'id', 'local', 'schema', 'table', 'field', 'delete', 'update')", [$params]);
1615 1
            throw new InvalidArgumentException("There are some missing elements for the addConstraint action");
1616
        }
1617
1618 5
        if (!in_array(strtoupper($params->delete), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1619 1
            $this->_logger->error("Invalid action for deletion on addConstraint");
1620 1
            throw new InvalidArgumentException("Invalid action for deletion on addConstraint");
1621
        }
1622
1623 4
        if (!in_array(strtoupper($params->update), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1624 1
            $this->_logger->error("Invalid action for update on addConstraint");
1625 1
            throw new InvalidArgumentException("Invalid action for update on addConstraint");
1626
        }
1627
1628 3
        if (is_array($params->field) && is_array($params->local)) {
1629 1
            $field = "`" . implode("`,`", $params->field) . "`";
1630 1
            $local = "`" . implode("`,`", $params->local) . "`";
1631 2
        } elseif (is_string($params->field) && is_string($params->local)) {
1632 1
            $field = "`{$params->field}`";
1633 1
            $local = "`{$params->local}`";
1634
        } else {
1635 1
            throw new InvalidArgumentException("Invalid type for the field and local values both must be an array or string");
1636
        }
1637 2
        $this->_sql .= " `{$params->id}` FOREIGN KEY ({$local}) REFERENCES `{$params->schema}`.`{$params->table}` ({$field}) ON DELETE {$params->delete} ON UPDATE {$params->update}";
1638
1639 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...
1640
            return $this->execute();
1641
        }
1642
1643 2
        return $this->_sql;
1644
    }
1645
1646
    /**
1647
     * Check to see if a field in a table exists
1648
     *
1649
     * @param string $strTableName
1650
     *            Table to check
1651
     * @param string $strFieldName
1652
     *            Field name to find
1653
     *
1654
     * @return bool Returns TRUE if field is found in that schema and table, otherwise FALSE
1655
     */
1656 2
    public function fieldExists(string $strTableName, string $strFieldName): bool
1657
    {
1658 2
        $fdata = $this->fieldData($strTableName);
1659
1660 2
        if (is_array($fdata) && count($fdata)) {
1661 2
            foreach ($fdata as $field) {
1662 2
                if ($field->name == $strFieldName) {
1663 1
                    return true;
1664
                }
1665
            }
1666
        }
1667
1668 1
        return false;
1669
    }
1670
1671
    /**
1672
     * Function to get the column data (datatype, flags, defaults, etc)
1673
     *
1674
     * @param string $strTableName
1675
     *            Table to query
1676
     * @param mixed $field
1677
     *            [optional]
1678
     *            Optional field to retrieve data (if null, returns data from all fields)
1679
     *
1680
     * @return mixed
1681
     */
1682 7
    public function fieldData(string $strTableName, $field = null)
1683
    {
1684 7
        if (is_null($field)) {
1685 3
            $res = $this->_c->query("SELECT * FROM $strTableName LIMIT 1");
1686 5
        } elseif (is_array($field)) {
1687 5
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $strTableName LIMIT 1");
1688 1
        } elseif (is_string($field)) {
1689 1
            $res = $this->_c->query("SELECT $field FROM $strTableName LIMIT 1");
1690
        } else {
1691 1
            return null;
1692
        }
1693
1694 7
        $fields = null;
1695 7
        if (is_a($res, 'mysqli_result')) {
1696 7
            $fields = $res->fetch_fields();
1697 7
            foreach ($fields as $i => $f) {
1698 7
                $fields["{$f->name}"] = $f;
1699 7
                unset($fields[$i]);
1700
            }
1701
        }
1702
1703 7
        return $fields;
1704
    }
1705
1706
    /**
1707
     * Function to check that all field parameters are set correctly
1708
     *
1709
     * @param stdClass $field_data
1710
     * @param stdClass $check
1711
     * @param array $pks
1712
     * @param stdClass $index
1713
     *
1714
     * @return array|string
1715
     */
1716 4
    public function fieldCheck(stdClass $field_data, stdClass $check, array $pks, ?array $index)
1717
    {
1718 4
        $default = null;
1719 4
        $ret = null;
1720
1721 4
        $nn = (isset($check->nn) && $check->nn ? " NOT NULL" : null);
1722
1723 4
        if ($check->default === null) {
1724 1
            $default = " DEFAULT NULL";
1725 3
        } elseif (strlen($check->default)) {
1726 1
            $default = " DEFAULT '{$check->default}'";
1727
        }
1728
1729 4
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1730 3
            $this->_logger->notice("Wrong datatype", [
1731 3
                'name' => $field_data->name,
1732 3
                'datatype' => $check->dataType
1733
            ]);
1734 3
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1735 1
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1736 1
            $this->_logger->notice("Incorrect size", [
1737 1
                'name' => $field_data->name,
1738 1
                'current' => $field_data->length,
1739 1
                'new_size' => $check->length
1740
            ]);
1741 1
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1742
        } elseif ($check->type == MYSQLI_TYPE_ENUM && ! ($field_data->flags & MYSQLI_ENUM_FLAG)) {
1743
            $this->_logger->notice("Setting ENUM type", [
1744
                'name' => $field_data->name,
1745
                'values' => implode(",", $check->values)
1746
            ]);
1747
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1748
        }
1749
1750 4
        if (! is_null($index) && count($index)) {
0 ignored issues
show
introduced by
The condition is_null($index) is always false.
Loading history...
1751
            foreach ($index as $ind) {
1752
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1753
                    $this->_logger->debug("Missing index", [
1754
                        'name' => $field_data->name
1755
                    ]);
1756
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1757
                }
1758
            }
1759
        }
1760
1761 4
        if (in_array($check->name, $pks) && ! ($field_data->flags & MYSQLI_PRI_KEY_FLAG)) {
1762
            $this->_logger->debug("Setting PKs", [
1763
                'keys' => implode(',', $pks)
1764
            ]);
1765
            $ret .= ($ret ? "," : "") . " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode("`,`", $pks) . "`)";
1766
        }
1767
1768 4
        return $ret;
1769
    }
1770
1771
    /**
1772
     * Function to check for the existence of a table within a schema
1773
     *
1774
     * @param string $strSchema
1775
     *            The schema to search in
1776
     * @param string $strTableName
1777
     *            Table to search for
1778
     *
1779
     * @return int|bool Returns number of tables that match if table is found in that schema, otherwise FALSE
1780
     *
1781
     * @throws InvalidArgumentException
1782
     */
1783 5
    public function tableExists($strSchema, $strTableName)
1784
    {
1785 5
        if (! $this->_c->select_db($strSchema)) {
1786 1
            $this->_logger->error("Schema {$strSchema} not found", [$this->_c->error]);
1787 1
            throw new InvalidArgumentException("Error connecting to schema {$strSchema}");
1788
        }
1789
1790 4
        if (preg_match("/[^0-9a-zA-Z\%\?\_]/", $strTableName)) {
1791 1
            $this->_logger->warning("Invalid table name {$strTableName}");
1792 1
            return false;
1793
        }
1794
1795 3
        $sql = "SHOW TABLES LIKE '{$strTableName}'";
1796
1797 3
        if ($res = $this->_c->query($sql)) {
1798 3
            if (gettype($res) == 'object' && is_a($res, 'mysqli_result') && $res->num_rows) {
1799 2
                return $res->num_rows;
1800
            }
1801
        } elseif ($this->_c->errno) {
1802
            $this->_logger->error($this->_c->error);
1803
        }
1804
1805 1
        return false;
1806
    }
1807
1808
    /**
1809
     * Function to detect if string is a JSON object or not
1810
     *
1811
     * @param string $strVal
1812
     *
1813
     * @return bool
1814
     */
1815 1
    public function isJson($strVal): bool
1816
    {
1817 1
        json_decode($strVal);
1818 1
        return (json_last_error() == JSON_ERROR_NONE);
1819
    }
1820
1821
    /**
1822
     * Function to escape SQL characters to prevent SQL injection
1823
     *
1824
     * @param mixed $val
1825
     *            Value to escape
1826
     * @param bool $blnEscape
1827
     *            Decide if we should escape or not
1828
     *
1829
     * @throws Exception
1830
     * @throws InvalidArgumentException
1831
     *
1832
     * @return string Escaped value
1833
     */
1834 132
    public function _escape($val, bool $blnEscape = true): string
1835
    {
1836 132
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1837 1
            return 'NULL';
1838 132
        } elseif (is_numeric($val) || is_string($val)) {
1839 132
            if ($blnEscape) {
1840 132
                return "'{$this->_c->real_escape_string($val)}'";
1841
            }
1842 1
            return $val;
1843 6
        } elseif (is_a($val, 'DateTime')) {
1844 1
            return "'{$val->format(MYSQL_DATETIME)}'";
1845 5
        } elseif (is_bool($val)) {
1846 1
            return $val ? "'1'" : "'0'";
1847 4
        } elseif (is_array($val)) {
1848 1
            $ret = [];
1849 1
            foreach ($val as $v) {
1850 1
                $ret[] = $this->_escape($v);
1851
            }
1852 1
            return "(" . implode(",", $ret) . ")";
1853 3
        } elseif (is_object($val) && method_exists($val, '_escape')) {
1854 2
            $ret = call_user_func([
1855 2
                $val,
1856 2
                '_escape'
1857
            ]);
1858 2
            if ($ret !== false && is_string($ret)) {
1859 1
                return $ret;
1860
            } else {
1861 1
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1862
            }
1863
        }
1864
1865 1
        throw new InvalidArgumentException("Unknown datatype to escape in SQL string {$this->_sql} " . gettype($val), E_ERROR);
1866
    }
1867
1868
    /**
1869
     * Function to populate the fields for the SQL
1870
     *
1871
     * @param array|string $fields
1872
     *            [optional]
1873
     *            Optional array of fields to string together to create a field list
1874
     *
1875
     * @return string|null
1876
     *
1877
     * @throws InvalidArgumentException
1878
     */
1879 37
    protected function fields($fields = null): string
1880
    {
1881 37
        $ret = null;
1882
1883 37
        if (is_array($fields) && count($fields) && isset($fields[0]) && is_string($fields[0])) {
1884 3
            foreach ($fields as $field) {
1885 3
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false) && stripos($field, 'IF(') === false) {
1886 2
                    $ret .= "`$field`,";
1887
                } else {
1888 1
                    $ret .= "$field,";
1889
                }
1890
            }
1891 3
            $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

1891
            $ret = substr(/** @scrutinizer ignore-type */ $ret, -1) == ',' ? substr($ret, 0, -1) : $ret;
Loading history...
1892 34
        } elseif (is_string($fields)) {
1893 4
            $ret = $fields;
1894 30
        } elseif (is_null($fields)) {
1895 29
            $ret = "*";
1896
        } else {
1897 1
            throw new InvalidArgumentException("Invalid field type");
1898
        }
1899
1900 36
        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...
1901
    }
1902
1903
    /**
1904
     * Function to parse the flags
1905
     *
1906
     * @param array $flags
1907
     *            Two-dimensional array to added flags
1908
     *
1909
     *            <code>
1910
     *            [
1911
     *            &nbsp;&nbsp;'group' => 'field',
1912
     *            &nbsp;&nbsp;'having' => 'field',
1913
     *            &nbsp;&nbsp;'order' => 'field',
1914
     *            &nbsp;&nbsp;'start' => 0,
1915
     *            &nbsp;&nbsp;'limit' => 0
1916
     *            ]
1917
     *            </code>
1918
     *
1919
     * @see Database::groups()
1920
     * @see Database::having()
1921
     * @see Database::order()
1922
     *
1923
     * @return string
1924
     */
1925 10
    protected function flags(array $arrFlags)
1926
    {
1927 10
        $ret = '';
1928
1929 10
        if (isset($arrFlags['group'])) {
1930 3
            $ret .= $this->groups($arrFlags['group']);
1931
        }
1932
1933 9
        if (isset($arrFlags['having']) && is_array($arrFlags['having'])) {
1934 1
            $having = " HAVING";
1935 1
            $this->_logger->debug("Parsing where clause and adding to query");
1936 1
            foreach ($arrFlags['having'] as $x => $h) {
1937 1
                if ($x > 0) {
1938 1
                    $having .= " {$h->sqlOperator}";
1939
                }
1940 1
                $having .= $h;
1941
            }
1942 1
            if (strlen($having) > strlen(" HAVING")) {
1943 1
                $ret .= $having;
1944
            }
1945
        }
1946
1947 9
        if (isset($arrFlags['order'])) {
1948 3
            $ret .= $this->order($arrFlags['order']);
1949
        }
1950
1951 8
        if (isset($arrFlags['limit']) && (is_string($arrFlags['limit']) || is_numeric($arrFlags['limit']))) {
1952 1
            $ret .= " LIMIT ";
1953 1
            if (isset($arrFlags['start']) && (is_string($arrFlags['start']) || is_numeric($arrFlags['start']))) {
1954 1
                $ret .= "{$arrFlags['start']},";
1955
            }
1956 1
            $ret .= "{$arrFlags['limit']}";
1957
        }
1958
1959 8
        return $ret;
1960
    }
1961
1962
    /**
1963
     * Function to parse SQL GROUP BY statements
1964
     *
1965
     * @param mixed $groups
1966
     *
1967
     * @return string
1968
     *
1969
     * @throws InvalidArgumentException
1970
     */
1971 3
    protected function groups($groups): string
1972
    {
1973 3
        $ret = '';
1974 3
        if (is_array($groups) && count($groups)) {
1975 1
            $ret .= " GROUP BY";
1976
1977 1
            foreach ($groups as $grp) {
1978 1
                $ret .= " $grp";
1979
1980 1
                if ($grp != end($groups)) {
1981 1
                    $ret .= ",";
1982
                }
1983
            }
1984 2
        } elseif (is_string($groups)) {
1985 1
            $ret .= " GROUP BY {$groups}";
1986
        } else {
1987 1
            throw new InvalidArgumentException("Error in datatype for groups " . gettype($groups), E_ERROR);
1988
        }
1989
1990 2
        return $ret;
1991
    }
1992
1993
    /**
1994
     * Function to parse SQL ORDER BY statements
1995
     *
1996
     * @param mixed $order
1997
     *
1998
     * @return string
1999
     *
2000
     * @throws InvalidArgumentException
2001
     */
2002 3
    protected function order($order): string
2003
    {
2004 3
        $ret = '';
2005 3
        if (is_array($order)) {
2006 1
            $ret .= " ORDER BY";
2007
2008 1
            foreach ($order as $ord) {
2009 1
                $ret .= " {$ord['field']} {$ord['sort']}";
2010
2011 1
                if ($ord != end($order)) {
2012 1
                    $ret .= ",";
2013
                }
2014
            }
2015 2
        } elseif (is_string($order)) {
2016 1
            $ret .= " ORDER BY {$order}";
2017
        } else {
2018 1
            throw new InvalidArgumentException("Error in datatype for order method ".gettype($order), E_ERROR);
2019
        }
2020
2021 2
        return $ret;
2022
    }
2023
2024
    /**
2025
     * Method to check if there are any invalid characters in the table name
2026
     *
2027
     * @param string $strTableName
2028
     *      Table name passed in
2029
     *
2030
     * @return bool
2031
     *      Returns FALSE if table name contains any characters that will be problematic (0-9, a-z, A-Z, $, _), TRUE otherwise
2032
     */
2033 84
    private function checkTableName(string $strTableName): bool
2034
    {
2035 84
        return !strlen($strTableName) || preg_match("/[^0-9a-zA-Z\$\_\ ]/", $strTableName) ? false : true;
2036
    }
2037
2038
    /**
2039
     * Function to see if a constraint exists
2040
     *
2041
     *
2042
     *
2043
     * @param string $strConstraintId
2044
     *
2045
     * @return bool
2046
     */
2047
    public function isConstraint($strConstraintId): bool
2048
    {
2049
        $res = $this->_c->query("SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = '{$strConstraintId}'");
2050
2051
        if ($res->num_rows) {
2052
            return true;
2053
        }
2054
2055
        return false;
2056
    }
2057
2058
    /**
2059
     * Method to add a where clause
2060
     *
2061
     * @param DBWhere|array:DBWhere $where
2062
     *
2063
     * @return bool|array:DBWhere
2064
     */
2065 51
    public function parseClause($where)
2066
    {
2067 51
        $ret = [];
2068 51
        $interfaces = [];
2069 51
        if (is_object($where)) {
2070 8
            $interfaces = class_implements($where);
2071
        }
2072 51
        if (is_array($where)) {
2073 42
            foreach ($where as $k => $w) {
2074 10
                if (!is_a($w, 'Godsgood33\Php_Db\DBWhere')) {
2075 1
                    return false;
2076
                }
2077 9
                $v = $this->_escape($w->value, $w->escape);
2078 9
                $where[$k]->value = $v;
2079
2080 9
                $ret[] = $where[$k];
2081
            }
2082 9
        } elseif (is_a($where, 'Godsgood33\Php_Db\DBWhere')) {
2083 8
            $v = $this->_escape($where->value, $where->escape);
2084 6
            $where->value = $v;
2085 6
            $ret[] = $where;
2086 2
        } elseif (in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable([$where, 'where'])) {
2087 1
            $ret = $this->parseClause($where->where());
2088
        } else {
2089 1
            $this->_logger->warning("Failed to get where", [$where]);
2090
        }
2091
2092 48
        return $ret;
2093
    }
2094
2095
    /**
2096
     * Encryption algorithm
2097
     *
2098
     * @param string $data
2099
     * @param string $salt
2100
     *
2101
     * @return string
2102
     *
2103
     * @throws Exception
2104
     *
2105
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
2106
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
2107
     */
2108 1
    public static function encrypt(string $data, ?string $salt = null)
2109
    {
2110 1
        if (!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
2111
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
2112
        }
2113
2114
        // Remove the base64 encoding from our key
2115 1
        if (is_null($salt)) {
2116 1
            $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
2117
        } else {
2118
            $encryption_key = base64_decode($salt);
2119
        }
2120
        // Generate an initialization vector
2121 1
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(PHP_DB_ENCRYPT_ALGORITHM));
2122
        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
2123 1
        $encrypted = openssl_encrypt($data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
2124
        // The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
2125 1
        return base64_encode($encrypted . '::' . $iv);
2126
    }
2127
2128
    /**
2129
     * Decryption algorithm
2130
     *
2131
     * @param string $data
2132
     *
2133
     * @return string
2134
     *
2135
     * @throws Exception
2136
     *
2137
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
2138
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
2139
     */
2140 132
    public static function decrypt(string $data)
2141
    {
2142 132
        if (!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
2143
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
2144
        }
2145
2146
        // Remove the base64 encoding from our key
2147 132
        $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
2148
2149
        // To decrypt, split the encrypted data from our IV - our unique separator used was "::"
2150 132
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
2151 132
        $plaintext = openssl_decrypt($encrypted_data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
2152 132
        return $plaintext;
2153
    }
2154
2155
    /**
2156
     * Method to check if all required fields are available in the object
2157
     *
2158
     * @param object $object
2159
     * @param array:string $requiredFields
2160
     *
2161
     * @return bool
2162
     */
2163 12
    public static function checkObject($object, $requiredFields): bool
2164
    {
2165 12
        $haystack = array_keys(json_decode(json_encode($object), true));
2166 12
        foreach ($requiredFields as $r) {
2167 12
            if (!in_array($r, $haystack)) {
2168 3
                return false;
2169
            }
2170
        }
2171
2172 9
        return true;
2173
    }
2174
2175
    /**
2176
     * Method to retrieve the error data
2177
     *
2178
     * @return string
2179
     */
2180 1
    public function error(): string
2181
    {
2182 1
        return $this->_c->error;
2183
    }
2184
2185
    /**
2186
     * Method to check the parameter types
2187
     *
2188
     * @param mixed $param
2189
     *
2190
     * @return int
2191
     *
2192
     * @throws MissingInterfaceAndMethods
2193
     * @throws Exception
2194
     */
2195 28
    private function checkParamType($param): int
2196
    {
2197
        // check for implented object interfaces
2198 28
        $interfaces = is_object($param) ? class_implements($param) : [];
2199
2200
        // numeric is the only datatype we can't have
2201 28
        if (is_numeric($param)) {
2202 1
            throw new MissingOrInvalidParam('Numeric parameters are not valid');
2203
        }
2204
2205
        // check for a SELECT statement within an insert
2206 27
        if (is_string($param) && stripos($param, 'select') !== false) {
2207 1
            return self::STRING;
2208
        }
2209
        // param is an object, check to see if it includes the required interface
2210 26
        elseif (is_object($param) && !is_iterable($param)) {
2211 10
            if (!in_array('Godsgood33\Php_Db\DBInterface', $interfaces)) {
2212 4
                throw new MissingInterfaceAndMethods('Object does not implement DBInterface interface');
2213
            }
2214
2215
            // check to see if all required methods are callable
2216 6
            if (!is_callable([$param, 'insert']) ||
2217 6
                !is_callable([$param, 'update']) ||
2218 6
                !is_callable([$param, 'replace']) ||
2219 6
                !is_callable([$param, 'where'])
2220
            ) {
2221
                throw new MissingInterfaceAndMethods("Required DBInterface methods are not present in class ".get_class($param));
2222
            }
2223
2224 6
            return self::OBJECT;
2225
        }
2226
        // param is an array
2227 20
        elseif (is_array($param)) {
2228
            // check that there is actual data in the array
2229 17
            if (!count($param)) {
2230
                throw new Exception('Array param is empty');
2231
            }
2232
2233
            // check the first element of the array and see if it's an object then recurse through to check it
2234 17
            $first = array_shift($param);
2235 17
            if (is_object($first) && $this->checkParamType($first) == self::OBJECT) {
2236 1
                return self::ARRAY_OBJECT;
2237
            }
2238
2239 15
            return self::ARRAY;
2240
        }
2241
        // object is potentially a collection
2242 3
        elseif (is_object($param)) {
2243
            // check that collection has required interface
2244 2
            if (!in_array('IteratorAggregate', $interfaces)) {
2245
                throw new MissingInterfaceAndMethods('Object does not implement IteratorAggregate interface');
2246
            }
2247
2248
            // get the first element in the collection
2249 2
            $it = $param->getIterator();
2250 2
            $first = $it->current();
2251
2252
            // check that the first element of the collection is a valid object as defined above
2253 2
            if ($this->checkParamType($first) == self::OBJECT) {
2254 2
                return self::COLLECTION;
2255
            }
2256
        }
2257
2258 1
        return 0;
2259
    }
2260
}
2261