Passed
Push — master ( 00142c...900183 )
by Ryan
03:06 queued 11s
created

Database::select()   C

Complexity

Conditions 15
Paths 48

Size

Total Lines 51
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 15.0102

Importance

Changes 6
Bugs 2 Features 0
Metric Value
cc 15
eloc 28
c 6
b 2
f 0
nc 48
nop 4
dl 0
loc 51
ccs 27
cts 28
cp 0.9643
crap 15.0102
rs 5.9166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
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 131
    public function __construct($strLogPath = __DIR__, mysqli &$dbh = null, $intLogLevel = null)
284
    {
285
        // set the log file path
286 131
        $this->_logPath = $strLogPath;
287 131
        if (!file_exists($this->_logPath)) {
288
            touch($this->_logPath . "/db.log");
289
        }
290
291
        // set the log level
292 131
        if (!defined("PHP_DB_LOG_LEVEL") && is_null($intLogLevel)) {
293
            $this->_logLevel = Logger::ERROR;
294 131
        } elseif (!is_null($intLogLevel)) {
295
            $this->_logLevel = $intLogLevel;
296 131
        } elseif (defined('PHP_DB_LOG_LEVEL')) {
297 131
            $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 131
        $this->_logger = new Logger('db', [
302 131
            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 131
        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 131
        if (! is_null($dbh) && is_a($dbh, 'mysqli')) {
314 1
            $this->_logger->debug("Connecting through existing connection");
315 1
            $this->_c = $dbh;
316 131
        } 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 131
        } 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 131
        if (defined('PHP_DB_ENCRYPT') && PHP_DB_ENCRYPT) {
326 131
            $this->_logger->debug("Decrypting password");
327 131
            $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 131
        $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 131
        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 131
        $this->_logger->info("Database connected");
343 131
        $this->_logger->debug("Connection details:", [
344 131
            'Server' => PHP_DB_SERVER,
345 131
            'User'   => PHP_DB_USER,
346 131
            'Schema' => PHP_DB_SCHEMA
347
        ]);
348
349 131
        $this->setVar("time_zone", "+00:00");
350 131
        $this->setVar("sql_mode", "");
351 131
    }
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 101
    private function setQueryType(int $qt): Database
421
    {
422 101
        $this->_logger->debug("Setting query type");
423 101
        $this->_queryType = $qt;
424
425 101
        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 67
    public function __toString(): string
434
    {
435 67
        $this->_logger->notice("__toString");
436 67
        $this->_logger->debug($this->_sql);
437 67
        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 131
    public function setVar(string $strName, ?string $strVal): bool
488
    {
489 131
        if (empty($strName)) {
490 1
            $this->_logger->debug("name is blank", [
491 1
                'name'  => $strName
492
            ]);
493 1
            return false;
494
        }
495
496 131
        $this->_logger->debug("Setting {$strName} = '{$strVal}'");
497
498 131
        if ($this->_c->real_query("SET $strName = {$this->_escape($strVal)}")) {
499 131
            $this->_logger->info("Var set");
500 131
            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 1
    public function extendedReplace(string $strTableName, array $arrFields, $params)
1140
    {
1141 1
        $this->_sql = null;
1142 1
        $this->setQueryType(self::EXTENDED_REPLACE);
1143
1144 1
        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 1
        if ($this->checkTableName($strTableName)) {
1149 1
            $this->_sql = "REPLACE INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
1150
        }
1151
1152 1
        $paramType = $this->checkParamType($params);
1153 1
        $this->_sql .= " VALUES ";
1154
1155 1
        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
        } elseif ($paramType == self::COLLECTION || $paramType == self::ARRAY_OBJECT) {
1167
            foreach ($params as $p) {
1168
                $key_value = $p->replace();
1169
                $this->_sql .= "(" . implode(",", array_map([
1170
                    $this,
1171
                    '_escape'
1172
                ], array_values($key_value))) . ")";
1173
1174
                if ($p != end($params)) {
1175
                    $this->_sql .= ",";
1176
                }
1177
            }
1178
        }
1179
1180 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...
1181
            return $this->execute();
1182
        }
1183
1184 1
        return $this->_sql;
1185
    }
1186
1187
    /**
1188
     * Function to build a delete statement
1189
     *
1190
     * @param string $strTableName
1191
     *            Table name to act on
1192
     * @param array $arrFields
1193
     *            [optional]
1194
     *            Optional list of fields to delete (used when including multiple tables)
1195
     * @param array:DBWhere|DBWhere $arrWhere
1196
     *            [optional]
1197
     *            Optional where clauses to use
1198
     * @param array $arrJoins
1199
     *            [optional]
1200
     *            Optional 2-dimensional array to add other flags
1201
     *
1202
     * @see Database::flags()
1203
     *
1204
     * @return string|NULL
1205
     *
1206
     * @throws InvalidArgumentException
1207
     *
1208
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1209
     */
1210 4
    public function delete(string $strTableName, ?array $arrFields = [], $arrWhere = [], ?array $arrJoins = [])
1211
    {
1212 4
        $this->_sql = "DELETE";
1213 4
        $this->setQueryType(self::DELETE);
1214
1215 4
        $this->_logger->debug("Deleting table data");
1216
1217 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...
1218 1
            $this->_sql .= " " . implode(",", $arrFields);
1219
        }
1220
1221 4
        if ($this->checkTableName($strTableName)) {
1222 4
            $this->_sql .= " FROM $strTableName";
1223
        }
1224
1225 4
        if (! is_null($arrJoins) && is_array($arrJoins) && count($arrJoins)) {
0 ignored issues
show
introduced by
The condition is_null($arrJoins) is always false.
Loading history...
introduced by
The condition is_array($arrJoins) is always true.
Loading history...
1226 1
            $this->_sql .= " " . implode(" ", $arrJoins);
1227
        }
1228
1229 4
        $where = $this->parseClause($arrWhere);
1230
1231 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...
1232 2
            $where_str = " WHERE";
1233 2
            $this->_logger->debug("Parsing where clause and adding to query");
1234 2
            foreach ($where as $x => $w) {
1235 2
                if ($x > 0) {
1236 1
                    $where_str .= " {$w->sqlOperator}";
1237
                }
1238 2
                $where_str .= $w;
1239
            }
1240 2
            if (strlen($where_str) > strlen(" WHERE")) {
1241 2
                $this->_sql .= $where_str;
1242
            }
1243
        }
1244
1245 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...
1246
            return $this->execute();
1247
        }
1248
1249 4
        return $this->_sql;
1250
    }
1251
1252
    /**
1253
     * Function to build a drop table statement (automatically executes)
1254
     *
1255
     * @param string $strTableName
1256
     *            Table to drop
1257
     * @param string $strType
1258
     *            [optional]
1259
     *            Type of item to drop ('table', 'view') (defaulted to 'table')
1260
     * @param bool $blnIsTemp
1261
     *            [optional]
1262
     *            Optional bool if this is a temporary table
1263
     *
1264
     * @return string|NULL
1265
     *
1266
     * @throws InvalidArgumentException
1267
     *
1268
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1269
     */
1270 5
    public function drop(string $strTableName, string $strType = 'table', bool $blnIsTemp = false)
1271
    {
1272 5
        $this->_sql = null;
1273 5
        $this->setQueryType(self::DROP);
1274
1275 5
        switch ($strType) {
1276 5
            case 'table':
1277 4
                $strType = 'TABLE';
1278 4
                break;
1279 1
            case 'view':
1280 1
                $strType = 'VIEW';
1281 1
                break;
1282
            default:
1283
                throw new InvalidArgumentException("Invalid type " . gettype($strType), E_ERROR);
1284
        }
1285
1286 5
        if ($this->checkTableName($strTableName)) {
1287 5
            $this->_sql = "DROP" . ($blnIsTemp ? " TEMPORARY" : "") . " $strType IF EXISTS `{$strTableName}`";
1288
        }
1289
1290 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...
1291
            return $this->execute();
1292
        }
1293
1294 5
        return $this->_sql;
1295
    }
1296
1297
    /**
1298
     * Function to build a truncate table statement (automatically executes)
1299
     *
1300
     * @param string $strTableName
1301
     *            Table to truncate
1302
     *
1303
     * @return string|NULL
1304
     *
1305
     * @throws InvalidArgumentException
1306
     *
1307
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1308
     */
1309 1
    public function truncate(string $strTableName)
1310
    {
1311 1
        $this->_sql = null;
1312 1
        $this->setQueryType(self::TRUNCATE);
1313
1314 1
        if ($this->checkTableName($strTableName)) {
1315 1
            $this->_sql = "TRUNCATE TABLE $strTableName";
1316
        }
1317
1318 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...
1319
            return $this->execute();
1320
        }
1321
1322 1
        return $this->_sql;
1323
    }
1324
1325
    /**
1326
     * Function to build a create temporary table statement
1327
     *
1328
     * @param string $strTableName
1329
     *            Name to give the table when creating
1330
     * @param bool $blnIsTemp
1331
     *            [optional]
1332
     *            Optional bool to make the table a temporary table
1333
     * @param mixed $strSelect
1334
     *            [optional]
1335
     *            Optional parameter if null uses last built statement
1336
     *            If string, will be made the SQL statement executed to create the table
1337
     *            If array, 2-dimensional array with "field", "datatype" values to build table fields
1338
     *
1339
     * @return NULL|string
1340
     *
1341
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1342
     */
1343 4
    public function createTable(string $strTableName, bool $blnIsTemp = false, $strSelect = null)
1344
    {
1345 4
        $this->setQueryType(self::CREATE_TABLE);
1346
1347 4
        if (is_null($strSelect) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1348 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($this->_sql)";
1349 3
        } elseif ($this->checkTableName($strTableName) && is_string($strSelect)) {
1350 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($strSelect)";
1351 2
        } elseif ($this->checkTableName($strTableName) && is_array($strSelect)) {
1352 2
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName (";
1353
1354 2
            foreach ($strSelect as $field) {
1355 2
                $default = null;
1356 2
                if (is_a($field, 'Godsgood33\Php_Db\DBCreateTable')) {
1357 2
                    $this->_sql .= (string) $field . ",";
1358
                } elseif (is_array($field)) {
1359
                    if (isset($field['default'])) {
1360
                        $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1361
                    }
1362
                    $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1363
                }
1364
            }
1365 2
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1366
        }
1367
1368 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...
1369
            return $this->execute();
1370
        }
1371
1372 4
        return $this->_sql;
1373
    }
1374
1375
    /**
1376
     * Function to create a table using a stdClass object derived from JSON
1377
     *
1378
     * @param stdClass $json
1379
     * @param bool $blnDropFirst bool to decide if you want to drop the table first
1380
     *
1381
     * @example /examples/create_table_json.json
1382
     *
1383
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1384
     */
1385 3
    public function createTableJson($json, bool $blnDropFirst = false)
1386
    {
1387 3
        $this->setQueryType(self::CREATE_TABLE);
1388 3
        $this->_c->select_db($json->schema);
1389
1390 3
        if ($blnDropFirst) {
1391
            $this->drop($json->name);
1392
            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...
1393
                $this->execute();
1394
            }
1395
        }
1396
1397 3
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1398 3
        foreach ($json->fields as $field) {
1399 3
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1400
1401 3
            if ($field->dataType == 'enum' && isset($field->values)) {
1402 1
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1403
            }
1404
1405 3
            if (isset($field->ai) && $field->ai) {
1406 3
                $this->_sql .= " AUTO_INCREMENT";
1407
            }
1408
1409 3
            if (isset($field->nn) && $field->nn) {
1410 3
                $this->_sql .= " NOT NULL";
1411 2
            } elseif (isset($field->default)) {
1412 2
                if (strtolower($field->default) == 'null') {
1413 1
                    $this->_sql .= " DEFAULT NULL";
1414 1
                } elseif (strlen($field->default)) {
1415 1
                    $this->_sql .= " DEFAULT '{$field->default}'";
1416
                }
1417
            }
1418
1419 3
            if ($field != end($json->fields)) {
1420 2
                $this->_sql .= ",";
1421
            }
1422
        }
1423
1424 3
        if (isset($json->index) && count($json->index)) {
1425 1
            foreach ($json->index as $ind) {
1426 1
                $ref = null;
1427 1
                if (is_array($ind->ref)) {
1428
                    $ref = "";
1429
                    foreach ($ind->ref as $r) {
1430
                        $ref .= "`{$r}` ASC,";
1431
                    }
1432
                    $ref = substr($ref, 0, -1);
1433 1
                } elseif (is_string($ind->ref)) {
1434 1
                    $ref = $ind->ref;
1435
                }
1436 1
                if (!is_null($ref)) {
1437 1
                    $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ref}`)";
1438
                }
1439
            }
1440
        }
1441
1442 3
        if (isset($json->constraints) && count($json->constraints)) {
1443
            foreach ($json->constraints as $con) {
1444
                $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));
1445
            }
1446
        }
1447
1448 3
        if (isset($json->unique) && count($json->unique)) {
1449 1
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1450
        }
1451
1452 3
        if (isset($json->primary_key) && count($json->primary_key)) {
1453 3
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1454
        } else {
1455
            if (substr($this->_sql, - 1) == ',') {
1456
                $this->_sql = substr($this->_sql, 0, - 1);
1457
            }
1458
1459
            $this->_sql .= ")";
1460
        }
1461
1462 3
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
1463
            return $this->execute();
1464
        }
1465
1466 3
        return $this->_sql;
1467
    }
1468
1469
    /**
1470
     * Method to add a column to the database (only one at a time!)
1471
     *
1472
     * @param string $strTableName
1473
     * @param stdClass $params
1474
     *
1475
     * @return string|mixed
1476
     *
1477
     * @throws InvalidArgumentException
1478
     *
1479
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1480
     */
1481 3
    public function addColumn(string $strTableName, stdClass $params)
1482
    {
1483 3
        $this->setQueryType(self::ALTER_TABLE);
1484 3
        $this->checkTableName($strTableName);
1485
1486 3
        $this->_sql = "ALTER TABLE {$strTableName} ADD COLUMN";
1487
1488 3
        if (!self::checkObject($params, ['name', 'dataType'])) {
1489 1
            $this->_logger->error("Missing elements for the addColumn method (need 'name', 'dataType')", [$params]);
1490 1
            throw new InvalidArgumentException("Missing elements for the addColumn method");
1491
        }
1492
1493 2
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1494 2
        $default = null;
1495 2
        if ($params->default === null) {
1496 1
            $default = " DEFAULT NULL";
1497 1
        } elseif (strlen($params->default)) {
1498 1
            $default = " DEFAULT {$this->_escape($params->default)}";
1499
        }
1500 2
        $this->_sql .= " `{$params->name}` {$params->dataType}" . $nn . $default;
1501
1502 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...
1503
            return $this->execute();
1504
        }
1505
1506 2
        return $this->_sql;
1507
    }
1508
1509
    /**
1510
     * Method to drop a fields from a table
1511
     *
1512
     * @param string $strTableName
1513
     * @param string|array:string $params
1514
     *
1515
     * @return string|mixed
1516
     *
1517
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1518
     */
1519 3
    public function dropColumn(string $strTableName, $params)
1520
    {
1521 3
        $this->setQueryType(self::ALTER_TABLE);
1522 3
        $this->_sql = "ALTER TABLE {$strTableName} DROP COLUMN";
1523
1524 3
        if (is_array($params) && count($params)) {
1525 2
            foreach ($params as $col) {
1526 2
                $this->_sql .= " `{$col->name}`";
1527
1528 2
                if ($col != end($params)) {
1529 1
                    $this->_sql .= ",";
1530
                }
1531
            }
1532 1
        } elseif (is_string($params)) {
1533 1
            $this->_sql .= " `{$params}`";
1534
        }
1535
1536 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...
1537
            return $this->execute();
1538
        }
1539
1540 3
        return $this->_sql;
1541
    }
1542
1543
    /**
1544
     * Method to modify a field to change it's datatype, name, or other parameter
1545
     *
1546
     * @param string $strTableName
1547
     * @param stdClass $params
1548
     *
1549
     * @return string|mixed
1550
     *
1551
     * @throws InvalidArgumentException
1552
     *
1553
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1554
     */
1555 3
    public function modifyColumn($strTableName, $params)
1556
    {
1557 3
        $this->setQueryType(self::ALTER_TABLE);
1558 3
        $this->_sql = "ALTER TABLE {$strTableName} MODIFY COLUMN";
1559
1560 3
        if (!self::checkObject($params, ['name', 'dataType'])) {
1561 1
            $this->_logger->error("Missing elements to the modifyColumn method (need 'name' and 'dataType')", [$params]);
1562 1
            throw new InvalidArgumentException("Missing elements to the modifyColumn method");
1563
        }
1564
1565 2
        if (!isset($params->new_name)) {
1566 1
            $params->new_name = $params->name;
1567
        }
1568
1569 2
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1570 2
        $default = null;
1571 2
        if ($params->default === null) {
1572 1
            $default = " DEFAULT NULL";
1573 1
        } elseif (strlen($params->default)) {
1574 1
            $default = " DEFAULT {$this->_escape($params->default)}";
1575
        }
1576 2
        $this->_sql .= " `{$params->name}` `{$params->new_name}` {$params->dataType}" . $nn . $default;
1577
1578 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...
1579
            return $this->execute();
1580
        }
1581
1582 2
        return $this->_sql;
1583
    }
1584
1585
    /**
1586
     * Method to add a constraint to a table
1587
     *
1588
     * @param string $strTableName
1589
     * @param stdClass $params
1590
     *
1591
     * @return string|mixed
1592
     *
1593
     * @throws InvalidArgumentException
1594
     *
1595
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1596
     */
1597 7
    public function addConstraint($strTableName, $params)
1598
    {
1599 7
        $this->setQueryType(self::ALTER_TABLE);
1600 7
        $this->_sql = "ALTER TABLE {$strTableName} ADD CONSTRAINT";
1601
1602 7
        if (!is_a($params, 'stdClass')) {
1603 1
            $this->_logger->critical("Error in reading constraint field");
1604 1
            throw new InvalidArgumentException("Error in reading constraint field");
1605
        }
1606
1607 6
        if (!self::checkObject($params, ['id', 'local', 'schema', 'table', 'field', 'delete', 'update'])) {
1608 1
            $this->_logger->error("Missing elements in the addConstraint method (need 'id', 'local', 'schema', 'table', 'field', 'delete', 'update')", [$params]);
1609 1
            throw new InvalidArgumentException("There are some missing elements for the addConstraint action");
1610
        }
1611
1612 5
        if (!in_array(strtoupper($params->delete), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1613 1
            $this->_logger->error("Invalid action for deletion on addConstraint");
1614 1
            throw new InvalidArgumentException("Invalid action for deletion on addConstraint");
1615
        }
1616
1617 4
        if (!in_array(strtoupper($params->update), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1618 1
            $this->_logger->error("Invalid action for update on addConstraint");
1619 1
            throw new InvalidArgumentException("Invalid action for update on addConstraint");
1620
        }
1621
1622 3
        if (is_array($params->field) && is_array($params->local)) {
1623 1
            $field = "`" . implode("`,`", $params->field) . "`";
1624 1
            $local = "`" . implode("`,`", $params->local) . "`";
1625 2
        } elseif (is_string($params->field) && is_string($params->local)) {
1626 1
            $field = "`{$params->field}`";
1627 1
            $local = "`{$params->local}`";
1628
        } else {
1629 1
            throw new InvalidArgumentException("Invalid type for the field and local values both must be an array or string");
1630
        }
1631 2
        $this->_sql .= " `{$params->id}` FOREIGN KEY ({$local}) REFERENCES `{$params->schema}`.`{$params->table}` ({$field}) ON DELETE {$params->delete} ON UPDATE {$params->update}";
1632
1633 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...
1634
            return $this->execute();
1635
        }
1636
1637 2
        return $this->_sql;
1638
    }
1639
1640
    /**
1641
     * Check to see if a field in a table exists
1642
     *
1643
     * @param string $strTableName
1644
     *            Table to check
1645
     * @param string $strFieldName
1646
     *            Field name to find
1647
     *
1648
     * @return bool Returns TRUE if field is found in that schema and table, otherwise FALSE
1649
     */
1650 2
    public function fieldExists(string $strTableName, string $strFieldName): bool
1651
    {
1652 2
        $fdata = $this->fieldData($strTableName);
1653
1654 2
        if (is_array($fdata) && count($fdata)) {
1655 2
            foreach ($fdata as $field) {
1656 2
                if ($field->name == $strFieldName) {
1657 1
                    return true;
1658
                }
1659
            }
1660
        }
1661
1662 1
        return false;
1663
    }
1664
1665
    /**
1666
     * Function to get the column data (datatype, flags, defaults, etc)
1667
     *
1668
     * @param string $strTableName
1669
     *            Table to query
1670
     * @param mixed $field
1671
     *            [optional]
1672
     *            Optional field to retrieve data (if null, returns data from all fields)
1673
     *
1674
     * @return mixed
1675
     */
1676 7
    public function fieldData(string $strTableName, $field = null)
1677
    {
1678 7
        if (is_null($field)) {
1679 3
            $res = $this->_c->query("SELECT * FROM $strTableName LIMIT 1");
1680 5
        } elseif (is_array($field)) {
1681 5
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $strTableName LIMIT 1");
1682 1
        } elseif (is_string($field)) {
1683 1
            $res = $this->_c->query("SELECT $field FROM $strTableName LIMIT 1");
1684
        } else {
1685 1
            return null;
1686
        }
1687
1688 7
        $fields = null;
1689 7
        if (is_a($res, 'mysqli_result')) {
1690 7
            $fields = $res->fetch_fields();
1691 7
            foreach ($fields as $i => $f) {
1692 7
                $fields["{$f->name}"] = $f;
1693 7
                unset($fields[$i]);
1694
            }
1695
        }
1696
1697 7
        return $fields;
1698
    }
1699
1700
    /**
1701
     * Function to check that all field parameters are set correctly
1702
     *
1703
     * @param stdClass $field_data
1704
     * @param stdClass $check
1705
     * @param array $pks
1706
     * @param stdClass $index
1707
     *
1708
     * @return array|string
1709
     */
1710 4
    public function fieldCheck(stdClass $field_data, stdClass $check, array $pks, ?array $index)
1711
    {
1712 4
        $default = null;
1713 4
        $ret = null;
1714
1715 4
        $nn = (isset($check->nn) && $check->nn ? " NOT NULL" : null);
1716
1717 4
        if ($check->default === null) {
1718 1
            $default = " DEFAULT NULL";
1719 3
        } elseif (strlen($check->default)) {
1720 1
            $default = " DEFAULT '{$check->default}'";
1721
        }
1722
1723 4
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1724 3
            $this->_logger->notice("Wrong datatype", [
1725 3
                'name' => $field_data->name,
1726 3
                'datatype' => $check->dataType
1727
            ]);
1728 3
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1729 1
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1730 1
            $this->_logger->notice("Incorrect size", [
1731 1
                'name' => $field_data->name,
1732 1
                'current' => $field_data->length,
1733 1
                'new_size' => $check->length
1734
            ]);
1735 1
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1736
        } elseif ($check->type == MYSQLI_TYPE_ENUM && ! ($field_data->flags & MYSQLI_ENUM_FLAG)) {
1737
            $this->_logger->notice("Setting ENUM type", [
1738
                'name' => $field_data->name,
1739
                'values' => implode(",", $check->values)
1740
            ]);
1741
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1742
        }
1743
1744 4
        if (! is_null($index) && count($index)) {
0 ignored issues
show
introduced by
The condition is_null($index) is always false.
Loading history...
1745
            foreach ($index as $ind) {
1746
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1747
                    $this->_logger->debug("Missing index", [
1748
                        'name' => $field_data->name
1749
                    ]);
1750
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1751
                }
1752
            }
1753
        }
1754
1755 4
        if (in_array($check->name, $pks) && ! ($field_data->flags & MYSQLI_PRI_KEY_FLAG)) {
1756
            $this->_logger->debug("Setting PKs", [
1757
                'keys' => implode(',', $pks)
1758
            ]);
1759
            $ret .= ($ret ? "," : "") . " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode("`,`", $pks) . "`)";
1760
        }
1761
1762 4
        return $ret;
1763
    }
1764
1765
    /**
1766
     * Function to check for the existence of a table within a schema
1767
     *
1768
     * @param string $strSchema
1769
     *            The schema to search in
1770
     * @param string $strTableName
1771
     *            Table to search for
1772
     *
1773
     * @return int|bool Returns number of tables that match if table is found in that schema, otherwise FALSE
1774
     *
1775
     * @throws InvalidArgumentException
1776
     */
1777 5
    public function tableExists($strSchema, $strTableName)
1778
    {
1779 5
        if (! $this->_c->select_db($strSchema)) {
1780 1
            $this->_logger->error("Schema {$strSchema} not found", [$this->_c->error]);
1781 1
            throw new InvalidArgumentException("Error connecting to schema {$strSchema}");
1782
        }
1783
1784 4
        if (preg_match("/[^0-9a-zA-Z\%\?\_]/", $strTableName)) {
1785 1
            $this->_logger->warning("Invalid table name {$strTableName}");
1786 1
            return false;
1787
        }
1788
1789 3
        $sql = "SHOW TABLES LIKE '{$strTableName}'";
1790
1791 3
        if ($res = $this->_c->query($sql)) {
1792 3
            if (gettype($res) == 'object' && is_a($res, 'mysqli_result') && $res->num_rows) {
1793 2
                return $res->num_rows;
1794
            }
1795
        } elseif ($this->_c->errno) {
1796
            $this->_logger->error($this->_c->error);
1797
        }
1798
1799 1
        return false;
1800
    }
1801
1802
    /**
1803
     * Function to detect if string is a JSON object or not
1804
     *
1805
     * @param string $strVal
1806
     *
1807
     * @return bool
1808
     */
1809 1
    public function isJson($strVal): bool
1810
    {
1811 1
        json_decode($strVal);
1812 1
        return (json_last_error() == JSON_ERROR_NONE);
1813
    }
1814
1815
    /**
1816
     * Function to escape SQL characters to prevent SQL injection
1817
     *
1818
     * @param mixed $val
1819
     *            Value to escape
1820
     * @param bool $blnEscape
1821
     *            Decide if we should escape or not
1822
     *
1823
     * @throws Exception
1824
     * @throws InvalidArgumentException
1825
     *
1826
     * @return string Escaped value
1827
     */
1828 131
    public function _escape($val, bool $blnEscape = true): string
1829
    {
1830 131
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1831 1
            return 'NULL';
1832 131
        } elseif (is_numeric($val) || is_string($val)) {
1833 131
            if ($blnEscape) {
1834 131
                return "'{$this->_c->real_escape_string($val)}'";
1835
            }
1836 1
            return $val;
1837 6
        } elseif (is_a($val, 'DateTime')) {
1838 1
            return "'{$val->format(MYSQL_DATETIME)}'";
1839 5
        } elseif (is_bool($val)) {
1840 1
            return $val ? "'1'" : "'0'";
1841 4
        } elseif (is_array($val)) {
1842 1
            $ret = [];
1843 1
            foreach ($val as $v) {
1844 1
                $ret[] = $this->_escape($v);
1845
            }
1846 1
            return "(" . implode(",", $ret) . ")";
1847 3
        } elseif (is_object($val) && method_exists($val, '_escape')) {
1848 2
            $ret = call_user_func([
1849 2
                $val,
1850 2
                '_escape'
1851
            ]);
1852 2
            if ($ret !== false && is_string($ret)) {
1853 1
                return $ret;
1854
            } else {
1855 1
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1856
            }
1857
        }
1858
1859 1
        throw new InvalidArgumentException("Unknown datatype to escape in SQL string {$this->_sql} " . gettype($val), E_ERROR);
1860
    }
1861
1862
    /**
1863
     * Function to populate the fields for the SQL
1864
     *
1865
     * @param array|string $fields
1866
     *            [optional]
1867
     *            Optional array of fields to string together to create a field list
1868
     *
1869
     * @return string|null
1870
     *
1871
     * @throws InvalidArgumentException
1872
     */
1873 37
    protected function fields($fields = null): string
1874
    {
1875 37
        $ret = null;
1876
1877 37
        if (is_array($fields) && count($fields) && isset($fields[0]) && is_string($fields[0])) {
1878 3
            foreach ($fields as $field) {
1879 3
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false) && stripos($field, 'IF(') === false) {
1880 2
                    $ret .= "`$field`,";
1881
                } else {
1882 1
                    $ret .= "$field,";
1883
                }
1884
            }
1885 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

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