Failed Conditions
Push — master ( e948fc...00142c )
by Ryan
12:27
created

Database::delete()   C

Complexity

Conditions 16
Paths 48

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 16.5

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 16
eloc 22
c 4
b 0
f 0
nc 48
nop 4
dl 0
loc 40
ccs 14
cts 16
cp 0.875
crap 16.5
rs 5.5666

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
namespace Godsgood33\Php_Db;
3
4
use mysqli;
5
use stdClass;
6
use Exception;
7
use InvalidArgumentException;
8
9
use Monolog\Logger;
10
use Monolog\Formatter\LineFormatter;
11
use Monolog\Handler\StreamHandler;
12
13
use Godsgood33\Php_Db\Exceptions\MissingOrInvalidParam;
14
use Godsgood33\Php_Db\Exceptions\ConnectError;
15
use Godsgood33\Php_Db\Exceptions\MissingInterfaceAndMethods;
16
use Godsgood33\Php_Db\Exceptions\QueryError;
17
18
/**
19
 * Constant to define that we want to return an object
20
 *
21
 * @var int
22
 */
23
define('MYSQLI_OBJECT', 4);
24
25
/**
26
 * Constant to return consistent date format
27
 *
28
 * @var string
29
 */
30
define('MYSQL_DATE', 'Y-m-d');
31
32
/**
33
 * Constant to return consistent datetime format
34
 *
35
 * @var string
36
 */
37
define('MYSQL_DATETIME', 'Y-m-d H:i:s');
38
39
/**
40
 * A generic database class
41
 *
42
 * @author Ryan Prather <[email protected]>
43
 */
44
class Database
45
{
46
47
    /**
48
     * 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 130
     * @access private
251
     * @var string
252
     */
253 130
    private $_logPath = null;
254 130
255
    /**
256
     * Variable to store the most recent insert ID from an insert query
257
     *
258
     * @access protected
259 130
     * @var mixed
260
     */
261 130
    protected $_insertId = null;
262
263 130
    /**
264 130
     * Constructor
265
     *
266
     * @param string $strLogPath
267
     *            [optional] absolute log path for the log file
268 130
     * @param mysqli $dbh
269 130
     *            [optional]
270
     *            [by ref]
271
     *            mysqli object to perform queries.
272
     * @param int $intLogLevel
273 130
     *            [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 130
     * @uses PHP_DB_LOG_LEVEL int to store the PSR-4 log level for the library
281 1
     * @uses PHP_DB_CLI_LOG bool to store if logs should be echoed to STDOUT
282 1
     */
283 130
    public function __construct($strLogPath = __DIR__, mysqli &$dbh = null, $intLogLevel = null)
284
    {
285
        // set the log file path
286 130
        $this->_logPath = $strLogPath;
287
        if (!file_exists($this->_logPath)) {
288
            touch($this->_logPath . "/db.log");
289
        }
290
291
        // set the log level
292 130
        if (!defined("PHP_DB_LOG_LEVEL") && is_null($intLogLevel)) {
293 130
            $this->_logLevel = Logger::ERROR;
294 130
        } elseif (!is_null($intLogLevel)) {
295
            $this->_logLevel = $intLogLevel;
296
        } elseif (defined('PHP_DB_LOG_LEVEL')) {
297
            $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 130
        // open the log handler
301
        $this->_logger = new Logger('db', [
302
            new StreamHandler(realpath($this->_logPath . "/db.log"), $this->_logLevel)
303 130
        ]);
304
305
        // check to see if we are operating in a CLI and if the user wants log data output to the terminal
306
        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 130
            $this->_logger->pushHandler($stream);
310 130
        }
311 130
312 130
        // check to see if a connection was passed and all defined constants are present to establish a connection
313 130
        if (! is_null($dbh) && is_a($dbh, 'mysqli')) {
314
            $this->_logger->debug("Connecting through existing connection");
315
            $this->_c = $dbh;
316 130
        } elseif (!defined('PHP_DB_SERVER') || !defined('PHP_DB_USER') || !defined('PHP_DB_PWD') || !defined('PHP_DB_SCHEMA')) {
317 130
            $this->_logger->critical("Missing essential defined constants");
318 130
            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
        } 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
        if (defined('PHP_DB_ENCRYPT') && PHP_DB_ENCRYPT) {
326
            $this->_logger->debug("Decrypting password");
327 17
            $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 17
            $pwd = PHP_DB_PWD;
330
        }
331 17
332 16
        // open the connection
333
        $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 1
335
        // check for a connection error and throw an error if there is one
336
        if ($this->_c->connect_errno) {
337 16
            $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
        $this->_logger->info("Database connected");
343
        $this->_logger->debug("Connection details:", [
344
            'Server' => PHP_DB_SERVER,
345
            'User'   => PHP_DB_USER,
346
            'Schema' => PHP_DB_SCHEMA
347 1
        ]);
348
349
        $this->setVar("time_zone", "+00:00");
350 1
        $this->setVar("sql_mode", "");
351 1
    }
352 1
353
    /**
354
     * Function to make sure that the database is connected
355
     *
356
     * @return bool
357
     *
358
     * @throws ConnectError
359
     */
360 1
    public function isConnected(): bool
361
    {
362 1
        $this->_logger->info("Checking for live connection");
363
364 1
        if (is_a($this->_c, 'mysqli')) {
365 1
            $ret = $this->_c->ping();
366
        } else {
367
            throw new ConnectError("Connection lost");
368
        }
369
370
        return $ret;
371
    }
372
373 1
    /**
374
     * Setter function for _logger
375 1
     *
376 1
     * @param Logger $log
377
     *
378
     * @return bool
379
     */
380
    public function setLogger(Logger $log): bool
381
    {
382
        // set the logger
383
        $this->_logger->debug("Setting logger");
384
        $this->_logger = $log;
385
        return true;
386
    }
387 100
388
    /**
389 100
     * Getter function for _logLevel
390 100
     *
391
     * @return int
392 100
     */
393
    public function getLogLevel(): int
394
    {
395
        $level = $this->_logLevel;
396
397
        $this->_logger->debug("Getting log level ({$level})");
398
        return $level;
399
    }
400 66
401
    /**
402 66
     * Getter function for _queryType
403 66
     *
404 66
     * @return int
405
     */
406
    public function getQueryType(): int
407
    {
408
        $this->_logger->debug("Getting query type");
409
        return $this->_queryType;
410
    }
411
412
    /**
413 1
     * Setter function for _queryType
414
     *
415 1
     * @param int $qt
416 1
     *      Class constants that store query type
417
     *
418 1
     * @return Database
419 1
     */
420
    private function setQueryType(int $qt): Database
421
    {
422
        $this->_logger->debug("Setting query type");
423
        $this->_queryType = $qt;
424
425
        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
    public function __toString(): string
434 2
    {
435
        $this->_logger->notice("__toString");
436 2
        $this->_logger->debug($this->_sql);
437 2
        return $this->_sql;
438 1
    }
439 1
440
    /**
441 1
     * 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
    public function getSchema()
447
    {
448
        if ($res = $this->_c->query("SELECT DATABASE()")) {
449
            $row = $res->fetch_row();
450
451
            $this->_logger->debug("Getting schema {$row[0]}");
452
            return $row[0];
453
        }
454 130
455
        return false;
456 130
    }
457 1
458 1
    /**
459
     * Function to set schema
460 1
     *
461
     * @param string $strSchema
462
     *
463 130
     * @return bool
464
     *
465 130
     * @throws QueryError
466 130
     */
467 130
    public function setSchema(string $strSchema): bool
468
    {
469 1
        $this->_logger->info("Setting schema to {$strSchema}");
470 1
        if (! $this->_c->select_db($strSchema)) {
471
            $this->_logger->error("Unknown schema {$strSchema}");
472
            throw new QueryError("Unknown database schema $strSchema");
473
        }
474
        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
    public function setVar(string $strName, ?string $strVal): bool
488
    {
489 15
        if (empty($strName)) {
490
            $this->_logger->debug("name is blank", [
491 15
                'name'  => $strName
492 2
            ]);
493
            return false;
494
        }
495 15
496 15
        $this->_logger->debug("Setting {$strName} = '{$strVal}'");
497
498 15
        if ($this->_c->real_query("SET $strName = {$this->_escape($strVal)}")) {
499
            $this->_logger->info("Var set");
500 15
            return true;
501 15
        } else {
502
            $this->_logger->error("Failed to set variable {$this->_c->error}");
503
            return false;
504 15
        }
505 15
    }
506 15
507
    /**
508 6
     * Function to execute the statement
509 6
     *
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 9
     *            Optional SQL query
516 9
     *
517 1
     * @throws QueryError
518 1
     * @throws ConnectError
519 1
     *
520
     * @return mixed
521
     */
522
    public function execute(int $return = MYSQLI_OBJECT, ?string $strSql = null)
523 14
    {
524 14
        if (! is_null($strSql)) {
525 1
            $this->_sql = $strSql;
526 1
        }
527
528
        $this->_result = false;
529 15
        $this->_insertId = null;
530
531
        $this->isConnected();
532
533
        $this->_logger->info("Executing {$this->_queryType} query");
534
        $this->_logger->debug($this->_sql);
535
536
        try {
537
            if (in_array($this->_queryType, [
538
                self::SELECT,
539
                self::SELECT_COUNT
540
            ])) {
541 14
                $this->_result = $this->_c->query($this->_sql);
542
                if ($this->_c->error) {
543 14
                    $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 14
                }
547 5
            } else {
548 9
                $this->_result = $this->_c->real_query($this->_sql);
549 2
                if ($this->_c->errno) {
550
                    $this->_logger->error("There was an error {$this->_c->error}");
551 2
                    $this->_logger->debug("Errored on query", [$this->_sql]);
552 2
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
553
                }
554 7
            }
555 1
556
            $this->_logger->debug("Checking for query results");
557
            $this->_result = $this->checkResults($return);
558
        } catch (QueryError $e) {
559
            $this->_logger->error($e);
560 1
        }
561 1
562 1
        return $this->_result;
563 1
    }
564 1
565
    /**
566 1
     * 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
    protected function checkResults(int $returnType)
575 1
    {
576
        $res = null;
577 6
578 6
        // check the sql results and process appropriately
579 1
        if (in_array($this->_queryType, [Database::CREATE_TABLE, Database::ALTER_TABLE, Database::TRUNCATE, Database::DROP])) {
580 5
            $res = $this->_result;
581 1
        } 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
            $res = $this->_c->affected_rows;
583
584 6
            if (in_array($this->_queryType, [Database::INSERT, Database::REPLACE, Database::EXTENDED_INSERT])) {
585 5
                $this->_insertId = $this->_c->insert_id;
586 3
            }
587 3
        } elseif ($this->_queryType == Database::SELECT_COUNT) {
588 3
            if (! is_a($this->_result, 'mysqli_result')) {
589
                $this->_logger->error("Error with return on query");
590
                return null;
591 2
            }
592
593
            if ($this->_result->num_rows == 1) {
594 1
                $row = $this->_result->fetch_assoc();
595 1
                if (isset($row['count'])) {
596
                    $this->_logger->debug("Returning SELECT_COUNT query", [
597
                        'count' => $row['count']
598
                    ]);
599 13
                    $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 13
                $res = $this->_result->num_rows;
606
            }
607
608
            mysqli_free_result($this->_result);
609
        } else {
610
            $method = "mysqli_fetch_object";
611
            if ($returnType == MYSQLI_ASSOC) {
612
                $method = "mysqli_fetch_assoc";
613
            } elseif ($returnType == MYSQLI_NUM) {
614
                $method = "mysqli_fetch_array";
615
            }
616
617
            if (is_a($this->_result, 'mysqli_result')) {
618 2
                if ($this->_result->num_rows > 1) {
619
                    $res = [];
620 2
                    while ($row = call_user_func($method, $this->_result)) {
621
                        $res[] = $row;
622
                    }
623
                } else {
624
                    $res = call_user_func($method, $this->_result);
625
                }
626
            } else {
627
                $this->_logger->error("Error with return on query");
628
                return null;
629
            }
630
        }
631
632
        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
        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 37
     *            [optional]
647
     *            Optional query to pass in and execute
648 37
     *
649 37
     * @return \mysqli_result|bool
650
     */
651
    public function query(?string $strSql = null)
652 37
    {
653 37
        return is_null($strSql) ? $this->_c->query($this->_sql) : $this->_c->query($strSql);
654 37
    }
655
656 36
    /**
657
     * A function to build a select query
658
     *
659
     * @param string $strTableName
660 36
     *            The table to query
661 1
     * @param array|string $fields
662 1
     *            [optional]
663
     *            Optional array of fields to return (defaults to '*')
664 1
     * @param array:DBWhere|DBWhere $arrWhere
665
     *            [optional] Where clause data
666 35
     * @param array $arrFlags
667
     *            [optional]
668
     *            Optional 2-dimensional array to allow other flags
669
     *
670 36
     * @see Database::flags()
671
     *
672 34
     * @throws Exception
673 8
     * @throws InvalidArgumentException
674 8
     *
675 8
     * @return mixed
676 8
     *
677 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
678
     */
679 8
    public function select(string $strTableName, $fields = null, $arrWhere = [], ?array $arrFlags = [])
680
    {
681 8
        $this->_sql = null;
682 8
        $this->setQueryType(self::SELECT);
683
684
        // starting building the query
685
        if ($this->checkTableName($strTableName)) {
686
            $this->_logger->debug("Starting SELECT query of {$strTableName}", [
687 34
                'fields' => $this->fields($fields)
688 9
            ]);
689 9
            $this->_sql = "SELECT " . $this->fields($fields) . " FROM $strTableName";
690
        }
691
692 32
        // add in any joins
693
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins']) && count($arrFlags['joins'])) {
694
            $this->_logger->debug("Adding joins", [
695
                'joins' => implode(' ', $arrFlags['joins'])
696 32
            ]);
697
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
698
        } else {
699
            $this->_logger->debug("No joins");
700
        }
701
702
        // parse the where clauses
703
        $where = $this->parseClause($arrWhere);
704
705
        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
            $where_str = " WHERE";
707
            $this->_logger->debug("Parsing where clause and adding to query");
708
            foreach ($where as $x => $w) {
709
                if ($x > 0) {
710
                    $where_str .= " {$w->sqlOperator}";
711
                }
712
                $where_str .= $w;
713
            }
714
            if (strlen($where_str) > strlen(" WHERE")) {
715
                $this->_sql .= $where_str;
716
            }
717
        }
718
719 4
        // search for any other flags (order, having, group)
720
        if (is_array($arrFlags) && count($arrFlags)) {
721 4
            $this->_logger->debug("Parsing flags and adding to query", $arrFlags);
722 4
            $this->_sql .= $this->flags($arrFlags);
723
        }
724
725 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...
726 4
            return $this->execute();
727
        }
728
729
        return $this->_sql;
730 4
    }
731 1
732
    /**
733
     * Function to build a query to check the number of rows in a table
734
     *
735 4
     * @param string $strTableName
736
     *            The table to query
737 4
     * @param array:DBWhere|DBWhere $arrWhere
738 2
     *            [optional]
739 2
     *            Optional 2-dimensional array to build where clause
740 2
     * @param array $arrFlags
741 2
     *            [optional]
742 1
     *            Optional 2-dimensional array to add flags
743
     *
744 2
     * @see Database::flags()
745
     *
746 2
     * @return string|int|NULL
747 2
     *
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 1
    {
754
        $this->_sql = null;
755
        $this->setQueryType(self::SELECT_COUNT);
756 4
757
        // start building query
758
        if ($this->checkTableName($strTableName)) {
759
            $this->_sql = "SELECT COUNT(1) AS 'count' FROM $strTableName";
760 4
        }
761
762
        // add in any joins
763
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
764
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
765
        }
766
767
        // parse where clauses
768
        $where = $this->parseClause($arrWhere);
769
770
        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
            $where_str = " WHERE";
772
            $this->_logger->debug("Parsing where clause and adding to query");
773
            foreach ($where as $x => $w) {
774
                if ($x > 0) {
775
                    $where_str .= " {$w->sqlOperator}";
776
                }
777 7
                $where_str .= $w;
778
            }
779 7
            if (strlen($where_str) > strlen(" WHERE")) {
780 7
                $this->_sql .= $where_str;
781
            }
782
        }
783 7
784 7
        // add in additional flags (group, having, order)
785
        if (is_array($arrFlags) && count($arrFlags)) {
786
            $this->_sql .= $this->flags($arrFlags);
787
        }
788 7
789 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...
790 3
            return $this->execute();
791
        }
792 3
793 3
        return $this->_sql;
794 3
    }
795 3
796 4
    /**
797 1
     * Function to build an insert query statement
798 3
     *
799 2
     * @param string $strTableName
800 2
     * @param mixed $arrParams
801 1
     * @param bool $blnToIgnore
802 1
     *
803 1
     * @return string|NULL
804
     *
805 1
     * @throws InvalidArgumentException
806
     * @throws MissingInterfaceAndMethods
807
     *
808 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
809
     */
810
    public function insert(string $strTableName, $params, bool $blnToIgnore = false)
811 5
    {
812
        $this->_sql = null;
813
        $this->setQueryType(self::INSERT);
814
815 5
        // start building query
816
        if ($this->checkTableName($strTableName)) {
817
            $this->_sql = "INSERT" . ($blnToIgnore ? " IGNORE" : "") . " INTO {$strTableName}";
818
        }
819
820
        $paramType = $this->checkParamType($params);
821
822
        // add in field parameters and values
823
        if ($paramType == self::ARRAY) {
824
            $keys = array_keys($params);
825
            $vals = array_values($params);
826
            $this->_sql .= " (`" . implode("`,`", $keys) . "`)";
827
            $this->_sql .= " VALUES (" . implode(",", array_map([
828
                $this,
829
                '_escape'
830
            ], $vals)) . ")";
831
        } elseif ($paramType == self::STRING) {
832
            $this->_sql .= " {$params}";
833
        } elseif ($paramType == self::OBJECT) {
834
            $arr = $params->insert();
835
            $keys = array_keys($arr);
836
            $vals = array_values($arr);
837
            $this->_sql .= " (`" . implode("`,`", $keys) . "`) VALUES ";
838
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], $vals)) . ")";
839 7
        } else {
840
            throw new InvalidArgumentException("Invalid type passed to insert " . gettype($params));
841 7
        }
842 7
843
        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 7
        }
846 7
847
        return $this->_sql;
848
    }
849 7
850 6
    /**
851 6
     * Function to create an extended insert query statement
852 4
     *
853 4
     * @param string $strTableName
854 2
     *            The table name that the data is going to be inserted on
855 2
     * @param array $arrFields
856 2
     *            An array of field names that each value represents
857
     * @param mixed $params
858 2
     *            An array of array of values or a string with a SELECT statement to populate the insert with
859
     * @param bool $blnToIgnore
860 3
     *            [optional]
861
     *            Boolean to decide if we need to use the INSERT IGNORE INTO syntax
862 3
     *
863 3
     * @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 2
     * @throws MissingOrInvalidParam
867 2
     * @throws MissingInterfaceAndMethods
868 2
     *
869 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
870
     */
871 1
    public function extendedInsert(string $strTableName, array $arrFields, $params, bool $blnToIgnore = false)
872 1
    {
873
        $this->_sql = null;
874
        $this->setQueryType(self::EXTENDED_INSERT);
875 1
876 1
        // start building query
877
        if ($this->checkTableName($strTableName)) {
878 1
            $this->_sql = "INSERT ".
879
                ($blnToIgnore ? "IGNORE " : "").
880
                "INTO $strTableName ".
881
                "(`".implode("`,`", $arrFields)."`)";
882
        }
883 1
884
        $paramType = $this->checkParamType($params);
885
        $this->_sql .= " VALUES ";
886 3
887
        if ($paramType == self::COLLECTION || $paramType == self::ARRAY_OBJECT) {
888
            foreach ($params as $p) {
889
                $key_value = $p->insert();
890 3
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
891
            }
892
893
            $this->_sql = substr($this->_sql, 0, -1);
894
        } elseif ($paramType == self::OBJECT) {
895
            $key_value = $params->insert();
896
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
897
        } elseif ($paramType == self::ARRAY) {
898
            foreach ($params as $p) {
899
                if (count($p) != count($arrFields)) {
900
                    $this->_logger->emergency("Inconsistent number of fields to values in extendedInsert", [
901
                        $p,
902
                        debug_backtrace()
903
                    ]);
904
                    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
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($p))) . ")";
907
908
                if ($p != end($params)) {
909
                    $this->_sql .= ",";
910
                }
911
            }
912
        } else {
913
            throw new InvalidArgumentException("Invalid param type ".gettype($params));
914
        }
915
916
        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 9
            return $this->execute();
918
        }
919 9
920 9
        return $this->_sql;
921
    }
922 9
923 9
    /**
924
     * Build a statement to update a table
925 9
     *
926 1
     * @param string $strTableName
927 1
     *            The table name to update
928
     * @param mixed $arrParams
929
     *            Name/value pairs of the field name and value
930 9
     * @param array:DBWhere|DBWhere $arrWhere
931
     *            [optional]
932
     *            DBWhere clauses
933 9
     * @param array $arrFlags
934 6
     *            [optional]
935 6
     *            Two-dimensional array to create other flag options (joins, order, and group)
936 6
     *
937 6
     * @see Database::flags()
938 5
     *
939
     * @return NULL|string
940
     *
941 6
     * @throws InvalidArgumentException
942 5
     * @throws MissingOrInvalidParam
943
     * @throws MissingInterfaceAndMethods
944 1
     *
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 6
    public function update(string $strTableName, $params, $arrWhere = [], ?array $arrFlags = [])
948 1
    {
949
        $this->_sql = "UPDATE ";
950
        $this->setQueryType(self::UPDATE);
951 3
952 2
        if ($this->checkTableName($strTableName)) {
953 2
            $this->_sql .= $strTableName;
954 1
955 1
            if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
956 1
                $this->_sql .= " " . implode(" ", $arrFlags['joins']);
957 1
                unset($arrFlags['joins']);
958 1
            }
959 1
960
            $this->_sql .= " SET ";
961 1
        }
962
963
        $paramType = $this->checkParamType($params);
964 1
965
        if ($paramType == self::ARRAY) {
966
            $keys = array_keys($params);
967 1
            foreach ($params as $f => $p) {
968
                $field = $f;
969
                if ((strpos($f, "`") === false) &&
970 7
                    (strpos($f, ".") === false) &&
971
                    (strpos($f, "*") === false) &&
972 7
                    (stripos($f, " as ") === false)
973 3
                ) {
974 3
                    $field = "`{$f}`";
975 3
                }
976 3
977 1
                if (! is_null($p)) {
978
                    $this->_sql .= "$field={$this->_escape($p)}";
979 3
                } else {
980
                    $this->_sql .= "$field=NULL";
981 3
                }
982 3
983
                if ($f != end($keys)) {
984
                    $this->_sql .= ",";
985
                }
986 7
            }
987
        } elseif ($paramType == self::OBJECT) {
988
            $key_value = $params->update();
989
            $fields = array_keys($key_value);
990 7
            $values = array_map([$this, '_escape'], array_values($key_value));
991
            foreach ($fields as $x => $f) {
992
                if ($x > 0) {
993
                    $this->_sql .= ",";
994 7
                }
995
                $this->_sql .= "`{$f}`={$values[$x]}";
996
            }
997
        } else {
998
            throw new MissingOrInvalidParam("No fields to update");
999
        }
1000
1001
        $where = $this->parseClause($arrWhere);
1002
1003
        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
            $where_str = " WHERE";
1005
            $this->_logger->debug("Parsing where clause and adding to query");
1006
            foreach ($where as $x => $w) {
1007
                if ($x > 0) {
1008
                    $where_str .= " {$w->sqlOperator}";
1009
                }
1010
                $where_str .= $w;
1011
            }
1012
            if (strlen($where_str) > strlen(" WHERE")) {
1013
                $this->_sql .= $where_str;
1014
            }
1015
        }
1016 2
1017
        if (! is_null($arrFlags) && is_array($arrFlags) && count($arrFlags)) {
1018 2
            $this->_sql .= $this->flags($arrFlags);
1019 2
        }
1020
1021 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...
1022 2
            return $this->execute();
1023
        }
1024
1025 2
        return $this->_sql;
1026 2
    }
1027 2
1028 2
    /**
1029
     * Function to offer an extended updated functionality by using two different tables.
1030
     *
1031 2
     * @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 2
     *            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 2
     *
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
    public function extendedUpdate(string $strTableToUpdate, string $strOriginalTable, string $strLinkField, array $arrParams)
1048
    {
1049
        $this->_sql = "UPDATE ";
1050
        $this->setQueryType(self::EXTENDED_UPDATE);
1051
1052
        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
            $this->_sql .= "$strTableToUpdate tbu INNER JOIN $strOriginalTable o USING ($strLinkField) SET ";
1054
        }
1055
1056
        if (is_array($arrParams) && count($arrParams)) {
1057 2
            foreach ($arrParams as $param) {
1058
                if ($param != $strLinkField) {
1059 2
                    $this->_sql .= "tbu.`$param` = o.`$param`,";
1060 2
                }
1061
            }
1062 2
            $this->_sql = substr($this->_sql, 0, - 1);
1063 2
        } elseif (is_string($arrParams)) {
1064
            $this->_sql .= "tbu.`$arrParams` = o.`$arrParams`";
1065
        } else {
1066 2
            throw new InvalidArgumentException("Do not understand datatype " . gettype($arrParams), E_ERROR);
1067 1
        }
1068 1
1069
        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 1
            return $this->execute();
1071 1
        }
1072 1
1073 1
        return $this->_sql;
1074 1
    }
1075 1
1076 1
    /**
1077 1
     * Function to build a replace query
1078 1
     *
1079 1
     * @param string $strTableName
1080 1
     *            The table to update
1081
     * @param mixed $arrParams
1082
     *            Name/value pair to insert
1083
     *
1084 2
     * @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
        $this->_sql = null;
1091
        $this->setQueryType(self::REPLACE);
1092
1093
        if ($this->checkTableName($strTableName)) {
1094
            $this->_sql = "REPLACE INTO $strTableName ";
1095
        }
1096
1097
        $paramType = $this->checkParamType($params);
1098
1099
        if ($paramType == self::ARRAY) {
1100
            $keys = array_keys($params);
1101
            $vals = array_values($params);
1102
1103
            $this->_sql .= "(`" . implode("`,`", $keys) . "`)";
1104
            $this->_sql .= " VALUES (" . implode(",", array_map([
1105
                $this,
1106
                '_escape'
1107 1
            ], array_values($vals))) . ")";
1108
        } elseif ($paramType == self::OBJECT) {
1109 1
            $key_value = $params->replace();
1110 1
            $this->_sql .= "(`" . implode("`,`", array_keys($key_value)) . "`) VALUES ";
1111
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . ")";
1112 1
        } else {
1113
            throw new MissingOrInvalidParam('Arrays or Objects that implement DBInterface are the only valid types for replace');
1114
        }
1115
1116 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...
1117 1
            return $this->execute();
1118
        }
1119
1120 1
        return $this->_sql;
1121 1
    }
1122 1
1123 1
    /**
1124 1
     * Function to build an extended replace statement
1125 1
     *
1126 1
     * @param string $strTableName
1127
     *            Table name to update
1128 1
     * @param array $arrFields
1129 1
     *            Array of fields
1130
     * @param mixed $arrParams
1131
     *            Two-dimensional array of values
1132
     *
1133
     * @return NULL|string
1134 1
     *
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 1
     */
1139
    public function extendedReplace(string $strTableName, array $arrFields, $params)
1140
    {
1141
        $this->_sql = null;
1142
        $this->setQueryType(self::EXTENDED_REPLACE);
1143
1144
        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
        if ($this->checkTableName($strTableName)) {
1149
            $this->_sql = "REPLACE INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
1150
        }
1151
1152
        $paramType = $this->checkParamType($params);
1153
        $this->_sql .= " VALUES ";
1154
1155
        if ($paramType == self::ARRAY) {
1156
            foreach ($params as $p) {
1157
                $this->_sql .= "(" . implode(",", array_map([
1158
                    $this,
1159
                    '_escape'
1160
                ], array_values($p))) . ")";
1161
1162
                if ($p != end($params)) {
1163
                    $this->_sql .= ",";
1164 4
                }
1165
            }
1166 4
        } elseif ($paramType == self::COLLECTION || $paramType == self::ARRAY_OBJECT) {
1167 4
            foreach ($params as $p) {
1168
                $key_value = $p->replace();
1169 4
                $this->_sql .= "(" . implode(",", array_map([
1170
                    $this,
1171 4
                    '_escape'
1172 1
                ], array_values($key_value))) . ")";
1173
1174
                if ($p != end($params)) {
1175 4
                    $this->_sql .= ",";
1176 4
                }
1177
            }
1178
        }
1179 4
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 4
1184
        return $this->_sql;
1185 4
    }
1186 2
1187 2
    /**
1188 2
     * Function to build a delete statement
1189 2
     *
1190 1
     * @param string $strTableName
1191
     *            Table name to act on
1192 2
     * @param array $arrFields
1193
     *            [optional]
1194 2
     *            Optional list of fields to delete (used when including multiple tables)
1195 2
     * @param array:DBWhere|DBWhere $arrWhere
1196
     *            [optional]
1197
     *            Optional where clauses to use
1198
     * @param array $arrJoins
1199 4
     *            [optional]
1200
     *            Optional 2-dimensional array to add other flags
1201
     *
1202
     * @see Database::flags()
1203 4
     *
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
    public function delete(string $strTableName, ?array $arrFields = [], $arrWhere = [], ?array $arrJoins = [])
1211
    {
1212
        $this->_sql = "DELETE";
1213
        $this->setQueryType(self::DELETE);
1214
1215
        $this->_logger->debug("Deleting table data");
1216
1217
        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
            $this->_sql .= " " . implode(",", $arrFields);
1219
        }
1220
1221
        if ($this->checkTableName($strTableName)) {
1222
            $this->_sql .= " FROM $strTableName";
1223
        }
1224 5
1225
        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 5
            $this->_sql .= " " . implode(" ", $arrJoins);
1227 5
        }
1228
1229 5
        $where = $this->parseClause($arrWhere);
1230 5
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 4
            $where_str = " WHERE";
1233 1
            $this->_logger->debug("Parsing where clause and adding to query");
1234 1
            foreach ($where as $x => $w) {
1235 1
                if ($x > 0) {
1236
                    $where_str .= " {$w->sqlOperator}";
1237
                }
1238
                $where_str .= $w;
1239
            }
1240 5
            if (strlen($where_str) > strlen(" WHERE")) {
1241 5
                $this->_sql .= $where_str;
1242
            }
1243
        }
1244 5
1245
        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 5
1249
        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 1
     *
1264
     * @return string|NULL
1265 1
     *
1266 1
     * @throws InvalidArgumentException
1267
     *
1268 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1269 1
     */
1270
    public function drop(string $strTableName, string $strType = 'table', bool $blnIsTemp = false)
1271
    {
1272 1
        $this->_sql = null;
1273
        $this->setQueryType(self::DROP);
1274
1275
        switch ($strType) {
1276 1
            case 'table':
1277
                $strType = 'TABLE';
1278
                break;
1279
            case 'view':
1280
                $strType = 'VIEW';
1281
                break;
1282
            default:
1283
                throw new InvalidArgumentException("Invalid type " . gettype($strType), E_ERROR);
1284
        }
1285
1286
        if ($this->checkTableName($strTableName)) {
1287
            $this->_sql = "DROP" . ($blnIsTemp ? " TEMPORARY" : "") . " $strType IF EXISTS `{$strTableName}`";
1288
        }
1289
1290
        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
        return $this->_sql;
1295
    }
1296
1297 4
    /**
1298
     * Function to build a truncate table statement (automatically executes)
1299 4
     *
1300
     * @param string $strTableName
1301 4
     *            Table to truncate
1302 1
     *
1303 3
     * @return string|NULL
1304 1
     *
1305 2
     * @throws InvalidArgumentException
1306 2
     *
1307
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1308 2
     */
1309 2
    public function truncate(string $strTableName)
1310 2
    {
1311 2
        $this->_sql = null;
1312
        $this->setQueryType(self::TRUNCATE);
1313
1314
        if ($this->checkTableName($strTableName)) {
1315
            $this->_sql = "TRUNCATE TABLE $strTableName";
1316
        }
1317
1318
        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 2
            return $this->execute();
1320
        }
1321
1322 4
        return $this->_sql;
1323
    }
1324
1325
    /**
1326 4
     * 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 3
     * @return NULL|string
1340
     *
1341 3
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1342 3
     */
1343
    public function createTable(string $strTableName, bool $blnIsTemp = false, $strSelect = null)
1344 3
    {
1345
        $this->setQueryType(self::CREATE_TABLE);
1346
1347
        if (is_null($strSelect) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1348
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($this->_sql)";
1349
        } elseif ($this->checkTableName($strTableName) && is_string($strSelect)) {
1350
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($strSelect)";
1351 3
        } elseif ($this->checkTableName($strTableName) && is_array($strSelect)) {
1352 3
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName (";
1353 3
1354
            foreach ($strSelect as $field) {
1355 3
                $default = null;
1356 1
                if (is_a($field, 'Godsgood33\Php_Db\DBCreateTable')) {
1357
                    $this->_sql .= (string) $field . ",";
1358
                } elseif (is_array($field)) {
1359 3
                    if (isset($field['default'])) {
1360 3
                        $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1361
                    }
1362
                    $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1363 3
                }
1364 3
            }
1365 2
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1366 2
        }
1367 1
1368 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...
1369 1
            return $this->execute();
1370
        }
1371
1372
        return $this->_sql;
1373 3
    }
1374 2
1375
    /**
1376
     * Function to create a table using a stdClass object derived from JSON
1377
     *
1378 3
     * @param stdClass $json
1379 1
     * @param bool $blnDropFirst bool to decide if you want to drop the table first
1380 1
     *
1381 1
     * @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
    public function createTableJson($json, bool $blnDropFirst = false)
1386
    {
1387 1
        $this->setQueryType(self::CREATE_TABLE);
1388 1
        $this->_c->select_db($json->schema);
1389
1390 1
        if ($blnDropFirst) {
1391 1
            $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 3
1397
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1398
        foreach ($json->fields as $field) {
1399
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1400
1401
            if ($field->dataType == 'enum' && isset($field->values)) {
1402 3
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1403 1
            }
1404
1405
            if (isset($field->ai) && $field->ai) {
1406 3
                $this->_sql .= " AUTO_INCREMENT";
1407 3
            }
1408
1409
            if (isset($field->nn) && $field->nn) {
1410
                $this->_sql .= " NOT NULL";
1411
            } elseif (isset($field->default)) {
1412
                if (strtolower($field->default) == 'null') {
1413
                    $this->_sql .= " DEFAULT NULL";
1414
                } elseif (strlen($field->default)) {
1415
                    $this->_sql .= " DEFAULT '{$field->default}'";
1416 3
                }
1417
            }
1418
1419
            if ($field != end($json->fields)) {
1420 3
                $this->_sql .= ",";
1421
            }
1422
        }
1423
1424
        if (isset($json->index) && count($json->index)) {
1425
            foreach ($json->index as $ind) {
1426
                $ref = null;
1427
                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
                } elseif (is_string($ind->ref)) {
1434
                    $ref = $ind->ref;
1435 3
                }
1436
                if (!is_null($ref)) {
1437 3
                    $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ref}`)";
1438 3
                }
1439
            }
1440 3
        }
1441
1442 3
        if (isset($json->constraints) && count($json->constraints)) {
1443 1
            foreach ($json->constraints as $con) {
1444 1
                $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 2
1448 2
        if (isset($json->unique) && count($json->unique)) {
1449 2
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1450 1
        }
1451 1
1452 1
        if (isset($json->primary_key) && count($json->primary_key)) {
1453
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1454 2
        } else {
1455
            if (substr($this->_sql, - 1) == ',') {
1456 2
                $this->_sql = substr($this->_sql, 0, - 1);
1457
            }
1458
1459
            $this->_sql .= ")";
1460 2
        }
1461
1462
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
1463
            return $this->execute();
1464
        }
1465
1466
        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 3
     * @param stdClass $params
1474
     *
1475 3
     * @return string|mixed
1476 3
     *
1477
     * @throws InvalidArgumentException
1478 3
     *
1479 2
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1480 2
     */
1481
    public function addColumn(string $strTableName, stdClass $params)
1482 2
    {
1483 1
        $this->setQueryType(self::ALTER_TABLE);
1484
        $this->checkTableName($strTableName);
1485
1486 1
        $this->_sql = "ALTER TABLE {$strTableName} ADD COLUMN";
1487 1
1488
        if (!self::checkObject($params, ['name', 'dataType'])) {
1489
            $this->_logger->error("Missing elements for the addColumn method (need 'name', 'dataType')", [$params]);
1490 3
            throw new InvalidArgumentException("Missing elements for the addColumn method");
1491
        }
1492
1493
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1494 3
        $default = null;
1495
        if ($params->default === null) {
1496
            $default = " DEFAULT NULL";
1497
        } elseif (strlen($params->default)) {
1498
            $default = " DEFAULT {$this->_escape($params->default)}";
1499
        }
1500
        $this->_sql .= " `{$params->name}` {$params->dataType}" . $nn . $default;
1501
1502
        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
        return $this->_sql;
1507
    }
1508
1509 3
    /**
1510
     * Method to drop a fields from a table
1511 3
     *
1512 3
     * @param string $strTableName
1513
     * @param string|array:string $params
1514 3
     *
1515 1
     * @return string|mixed
1516 1
     *
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 2
    public function dropColumn(string $strTableName, $params)
1520 1
    {
1521
        $this->setQueryType(self::ALTER_TABLE);
1522
        $this->_sql = "ALTER TABLE {$strTableName} DROP COLUMN";
1523 2
1524 2
        if (is_array($params) && count($params)) {
1525 2
            foreach ($params as $col) {
1526 1
                $this->_sql .= " `{$col->name}`";
1527 1
1528 1
                if ($col != end($params)) {
1529
                    $this->_sql .= ",";
1530 2
                }
1531
            }
1532 2
        } elseif (is_string($params)) {
1533
            $this->_sql .= " `{$params}`";
1534
        }
1535
1536 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...
1537
            return $this->execute();
1538
        }
1539
1540
        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 7
     * @throws InvalidArgumentException
1552
     *
1553 7
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1554 7
     */
1555
    public function modifyColumn($strTableName, $params)
1556 7
    {
1557 1
        $this->setQueryType(self::ALTER_TABLE);
1558 1
        $this->_sql = "ALTER TABLE {$strTableName} MODIFY COLUMN";
1559
1560
        if (!self::checkObject($params, ['name', 'dataType'])) {
1561 6
            $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 1
        }
1564
1565
        if (!isset($params->new_name)) {
1566 5
            $params->new_name = $params->name;
1567 1
        }
1568 1
1569
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1570
        $default = null;
1571 4
        if ($params->default === null) {
1572 1
            $default = " DEFAULT NULL";
1573 1
        } elseif (strlen($params->default)) {
1574
            $default = " DEFAULT {$this->_escape($params->default)}";
1575
        }
1576 3
        $this->_sql .= " `{$params->name}` `{$params->new_name}` {$params->dataType}" . $nn . $default;
1577 1
1578 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...
1579 2
            return $this->execute();
1580 1
        }
1581 1
1582
        return $this->_sql;
1583 1
    }
1584
1585 2
    /**
1586
     * Method to add a constraint to a table
1587 2
     *
1588
     * @param string $strTableName
1589
     * @param stdClass $params
1590
     *
1591 2
     * @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
    public function addConstraint($strTableName, $params)
1598
    {
1599
        $this->setQueryType(self::ALTER_TABLE);
1600
        $this->_sql = "ALTER TABLE {$strTableName} ADD CONSTRAINT";
1601
1602
        if (!is_a($params, 'stdClass')) {
1603
            $this->_logger->critical("Error in reading constraint field");
1604 2
            throw new InvalidArgumentException("Error in reading constraint field");
1605
        }
1606 2
1607
        if (!self::checkObject($params, ['id', 'local', 'schema', 'table', 'field', 'delete', 'update'])) {
1608 2
            $this->_logger->error("Missing elements in the addConstraint method (need 'id', 'local', 'schema', 'table', 'field', 'delete', 'update')", [$params]);
1609 2
            throw new InvalidArgumentException("There are some missing elements for the addConstraint action");
1610 2
        }
1611 1
1612
        if (!in_array(strtoupper($params->delete), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1613
            $this->_logger->error("Invalid action for deletion on addConstraint");
1614
            throw new InvalidArgumentException("Invalid action for deletion on addConstraint");
1615
        }
1616 1
1617
        if (!in_array(strtoupper($params->update), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1618
            $this->_logger->error("Invalid action for update on addConstraint");
1619
            throw new InvalidArgumentException("Invalid action for update on addConstraint");
1620
        }
1621
1622
        if (is_array($params->field) && is_array($params->local)) {
1623
            $field = "`" . implode("`,`", $params->field) . "`";
1624
            $local = "`" . implode("`,`", $params->local) . "`";
1625
        } elseif (is_string($params->field) && is_string($params->local)) {
1626
            $field = "`{$params->field}`";
1627
            $local = "`{$params->local}`";
1628
        } else {
1629
            throw new InvalidArgumentException("Invalid type for the field and local values both must be an array or string");
1630 7
        }
1631
        $this->_sql .= " `{$params->id}` FOREIGN KEY ({$local}) REFERENCES `{$params->schema}`.`{$params->table}` ({$field}) ON DELETE {$params->delete} ON UPDATE {$params->update}";
1632 7
1633 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...
1634 5
            return $this->execute();
1635 5
        }
1636 1
1637 1
        return $this->_sql;
1638
    }
1639 1
1640
    /**
1641
     * Check to see if a field in a table exists
1642 7
     *
1643 7
     * @param string $strTableName
1644 7
     *            Table to check
1645 7
     * @param string $strFieldName
1646 7
     *            Field name to find
1647 7
     *
1648
     * @return bool Returns TRUE if field is found in that schema and table, otherwise FALSE
1649
     */
1650
    public function fieldExists(string $strTableName, string $strFieldName): bool
1651 7
    {
1652
        $fdata = $this->fieldData($strTableName);
1653
1654
        if (is_array($fdata) && count($fdata)) {
1655
            foreach ($fdata as $field) {
1656
                if ($field->name == $strFieldName) {
1657
                    return true;
1658
                }
1659
            }
1660
        }
1661
1662
        return false;
1663
    }
1664 4
1665
    /**
1666 4
     * Function to get the column data (datatype, flags, defaults, etc)
1667 4
     *
1668
     * @param string $strTableName
1669 4
     *            Table to query
1670
     * @param mixed $field
1671 4
     *            [optional]
1672 1
     *            Optional field to retrieve data (if null, returns data from all fields)
1673 3
     *
1674 1
     * @return mixed
1675
     */
1676
    public function fieldData(string $strTableName, $field = null)
1677 4
    {
1678 3
        if (is_null($field)) {
1679 3
            $res = $this->_c->query("SELECT * FROM $strTableName LIMIT 1");
1680 3
        } elseif (is_array($field)) {
1681
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $strTableName LIMIT 1");
1682 3
        } elseif (is_string($field)) {
1683 1
            $res = $this->_c->query("SELECT $field FROM $strTableName LIMIT 1");
1684 1
        } else {
1685 1
            return null;
1686 1
        }
1687 1
1688
        $fields = null;
1689 1
        if (is_a($res, 'mysqli_result')) {
1690
            $fields = $res->fetch_fields();
1691
            foreach ($fields as $i => $f) {
1692
                $fields["{$f->name}"] = $f;
1693
                unset($fields[$i]);
1694
            }
1695
        }
1696
1697
        return $fields;
1698 4
    }
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 4
     */
1710
    public function fieldCheck(stdClass $field_data, stdClass $check, array $pks, ?array $index)
1711
    {
1712
        $default = null;
1713
        $ret = null;
1714
1715
        $nn = (isset($check->nn) && $check->nn ? " NOT NULL" : null);
1716 4
1717
        if ($check->default === null) {
1718
            $default = " DEFAULT NULL";
1719
        } elseif (strlen($check->default)) {
1720
            $default = " DEFAULT '{$check->default}'";
1721
        }
1722
1723
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1724
            $this->_logger->notice("Wrong datatype", [
1725
                'name' => $field_data->name,
1726
                'datatype' => $check->dataType
1727
            ]);
1728
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1729
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1730
            $this->_logger->notice("Incorrect size", [
1731 5
                'name' => $field_data->name,
1732
                'current' => $field_data->length,
1733 5
                'new_size' => $check->length
1734 1
            ]);
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 4
                'name' => $field_data->name,
1739 1
                'values' => implode(",", $check->values)
1740 1
            ]);
1741
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1742
        }
1743 3
1744
        if (! is_null($index) && count($index)) {
0 ignored issues
show
introduced by
The condition is_null($index) is always false.
Loading history...
1745 3
            foreach ($index as $ind) {
1746 3
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1747 2
                    $this->_logger->debug("Missing index", [
1748
                        'name' => $field_data->name
1749
                    ]);
1750
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1751
                }
1752
            }
1753 1
        }
1754
1755
        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
        return $ret;
1763 1
    }
1764
1765 1
    /**
1766 1
     * 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
    public function tableExists($strSchema, $strTableName)
1778
    {
1779
        if (! $this->_c->select_db($strSchema)) {
1780
            $this->_logger->error("Schema {$strSchema} not found", [$this->_c->error]);
1781
            throw new InvalidArgumentException("Error connecting to schema {$strSchema}");
1782 130
        }
1783
1784 130
        if (preg_match("/[^0-9a-zA-Z\%\?\_]/", $strTableName)) {
1785 1
            $this->_logger->warning("Invalid table name {$strTableName}");
1786 130
            return false;
1787 130
        }
1788 130
1789
        $sql = "SHOW TABLES LIKE '{$strTableName}'";
1790 1
1791 6
        if ($res = $this->_c->query($sql)) {
1792 1
            if (gettype($res) == 'object' && is_a($res, 'mysqli_result') && $res->num_rows) {
1793 5
                return $res->num_rows;
1794 1
            }
1795 4
        } elseif ($this->_c->errno) {
1796 1
            $this->_logger->error($this->_c->error);
1797 1
        }
1798 1
1799
        return false;
1800 1
    }
1801 3
1802 2
    /**
1803 2
     * Function to detect if string is a JSON object or not
1804 2
     *
1805
     * @param string $strVal
1806 2
     *
1807 1
     * @return bool
1808
     */
1809 1
    public function isJson($strVal): bool
1810
    {
1811
        json_decode($strVal);
1812
        return (json_last_error() == JSON_ERROR_NONE);
1813 1
    }
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 37
     */
1828
    public function _escape($val, bool $blnEscape = true): string
1829 37
    {
1830
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1831 37
            return 'NULL';
1832 3
        } elseif (is_numeric($val) || is_string($val)) {
1833 3
            if ($blnEscape) {
1834 2
                return "'{$this->_c->real_escape_string($val)}'";
1835
            }
1836 1
            return $val;
1837
        } elseif (is_a($val, 'DateTime')) {
1838
            return "'{$val->format(MYSQL_DATETIME)}'";
1839 3
        } elseif (is_bool($val)) {
1840 34
            return $val ? "'1'" : "'0'";
1841 4
        } elseif (is_array($val)) {
1842 30
            $ret = [];
1843 29
            foreach ($val as $v) {
1844
                $ret[] = $this->_escape($v);
1845 1
            }
1846
            return "(" . implode(",", $ret) . ")";
1847
        } elseif (is_object($val) && method_exists($val, '_escape')) {
1848 36
            $ret = call_user_func([
1849
                $val,
1850
                '_escape'
1851
            ]);
1852
            if ($ret !== false && is_string($ret)) {
1853
                return $ret;
1854
            } else {
1855
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1856
            }
1857
        }
1858
1859
        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 10
    protected function fields($fields = null): string
1874
    {
1875 10
        $ret = null;
1876
1877 10
        if (is_array($fields) && count($fields) && isset($fields[0]) && is_string($fields[0])) {
1878 3
            foreach ($fields as $field) {
1879
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false) && stripos($field, 'IF(') === false) {
1880
                    $ret .= "`$field`,";
1881 9
                } else {
1882 1
                    $ret .= "$field,";
1883 1
                }
1884 1
            }
1885 1
            $ret = substr($ret, -1) == ',' ? substr($ret, 0, -1) : $ret;
0 ignored issues
show
Bug introduced by
It seems like $ret can also be of type null; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1885
            $ret = substr(/** @scrutinizer ignore-type */ $ret, -1) == ',' ? substr($ret, 0, -1) : $ret;
Loading history...
1886 1
        } elseif (is_string($fields)) {
1887
            $ret = $fields;
1888 1
        } elseif (is_null($fields)) {
1889
            $ret = "*";
1890 1
        } else {
1891 1
            throw new InvalidArgumentException("Invalid field type");
1892
        }
1893
1894
        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 9
    }
1896 3
1897
    /**
1898
     * Function to parse the flags
1899 8
     *
1900 1
     * @param array $flags
1901 1
     *            Two-dimensional array to added flags
1902 1
     *
1903
     *            <code>
1904 1
     *            [
1905
     *            &nbsp;&nbsp;'group' => 'field',
1906
     *            &nbsp;&nbsp;'having' => 'field',
1907 8
     *            &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 3
    protected function flags(array $arrFlags)
1920
    {
1921 3
        $ret = '';
1922 3
1923 1
        if (isset($arrFlags['group'])) {
1924
            $ret .= $this->groups($arrFlags['group']);
1925 1
        }
1926 1
1927
        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
            foreach ($arrFlags['having'] as $x => $h) {
1931
                if ($x > 0) {
1932 2
                    $having .= " {$h->sqlOperator}";
1933 1
                }
1934
                $having .= $h;
1935 1
            }
1936
            if (strlen($having) > strlen(" HAVING")) {
1937
                $ret .= $having;
1938 2
            }
1939
        }
1940
1941
        if (isset($arrFlags['order'])) {
1942
            $ret .= $this->order($arrFlags['order']);
1943
        }
1944
1945
        if (isset($arrFlags['limit']) && (is_string($arrFlags['limit']) || is_numeric($arrFlags['limit']))) {
1946
            $ret .= " LIMIT ";
1947
            if (isset($arrFlags['start']) && (is_string($arrFlags['start']) || is_numeric($arrFlags['start']))) {
1948
                $ret .= "{$arrFlags['start']},";
1949
            }
1950 3
            $ret .= "{$arrFlags['limit']}";
1951
        }
1952 3
1953 3
        return $ret;
1954 1
    }
1955
1956 1
    /**
1957 1
     * Function to parse SQL GROUP BY statements
1958
     *
1959 1
     * @param mixed $groups
1960 1
     *
1961
     * @return string
1962
     *
1963 2
     * @throws InvalidArgumentException
1964 1
     */
1965
    protected function groups($groups): string
1966 1
    {
1967
        $ret = '';
1968
        if (is_array($groups) && count($groups)) {
1969 2
            $ret .= " GROUP BY";
1970
1971
            foreach ($groups as $grp) {
1972
                $ret .= " $grp";
1973
1974
                if ($grp != end($groups)) {
1975
                    $ret .= ",";
1976
                }
1977
            }
1978
        } elseif (is_string($groups)) {
1979
            $ret .= " GROUP BY {$groups}";
1980
        } else {
1981 82
            throw new InvalidArgumentException("Error in datatype for groups " . gettype($groups), E_ERROR);
1982
        }
1983 82
1984
        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
    protected function order($order): string
1997
    {
1998
        $ret = '';
1999
        if (is_array($order)) {
2000
            $ret .= " ORDER BY";
2001
2002
            foreach ($order as $ord) {
2003
                $ret .= " {$ord['field']} {$ord['sort']}";
2004
2005
                if ($ord != end($order)) {
2006
                    $ret .= ",";
2007
                }
2008
            }
2009
        } elseif (is_string($order)) {
2010
            $ret .= " ORDER BY {$order}";
2011
        } else {
2012
            throw new InvalidArgumentException("Error in datatype for order method ".gettype($order), E_ERROR);
2013 51
        }
2014
2015 51
        return $ret;
2016 51
    }
2017 51
2018 8
    /**
2019
     * Method to check if there are any invalid characters in the table name
2020 51
     *
2021 42
     * @param string $strTableName
2022 10
     *      Table name passed in
2023 1
     *
2024
     * @return bool
2025 9
     *      Returns FALSE if table name contains any characters that will be problematic (0-9, a-z, A-Z, $, _), TRUE otherwise
2026 9
     */
2027
    private function checkTableName(string $strTableName): bool
2028 9
    {
2029
        return !strlen($strTableName) || preg_match("/[^0-9a-zA-Z\$\_\ ]/", $strTableName) ? false : true;
2030 9
    }
2031 8
2032 6
    /**
2033 6
     * Function to see if a constraint exists
2034 2
     *
2035 1
     *
2036 1
     *
2037
     * @param string $strConstraintId
2038 1
     *
2039
     * @return bool
2040
     */
2041 48
    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 1
     * @return bool|array:DBWhere
2058
     */
2059 1
    public function parseClause($where)
2060
    {
2061
        $ret = [];
2062
        $interfaces = [];
2063
        if (is_object($where)) {
2064 1
            $interfaces = class_implements($where);
2065 1
        }
2066
        if (is_array($where)) {
2067
            foreach ($where as $k => $w) {
2068
                if (!is_a($w, 'Godsgood33\Php_Db\DBWhere')) {
2069
                    return false;
2070 1
                }
2071
                $v = $this->_escape($w->value, $w->escape);
2072 1
                $where[$k]->value = $v;
2073
2074 1
                $ret[] = $where[$k];
2075
            }
2076
        } elseif (is_a($where, 'Godsgood33\Php_Db\DBWhere')) {
2077
            $v = $this->_escape($where->value, $where->escape);
2078
            $where->value = $v;
2079
            $ret[] = $where;
2080
        } elseif (in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable([$where, 'where'])) {
2081
            $ret = $this->parseClause($where->where());
2082
        } else {
2083
            $this->_logger->warning("Failed to get where", [$where]);
2084
        }
2085
2086
        return $ret;
2087
    }
2088
2089 130
    /**
2090
     * Encryption algorithm
2091 130
     *
2092
     * @param string $data
2093
     * @param string $salt
2094
     *
2095
     * @return string
2096 130
     *
2097
     * @throws Exception
2098
     *
2099 130
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
2100 130
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
2101 130
     */
2102
    public static function encrypt(string $data, ?string $salt = null)
2103
    {
2104
        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
        if (is_null($salt)) {
2110
            $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
2111
        } else {
2112 12
            $encryption_key = base64_decode($salt);
2113
        }
2114 12
        // Generate an initialization vector
2115 12
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(PHP_DB_ENCRYPT_ALGORITHM));
2116 12
        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
2117 3
        $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
        return base64_encode($encrypted . '::' . $iv);
2120
    }
2121 9
2122
    /**
2123
     * Decryption algorithm
2124
     *
2125
     * @param string $data
2126
     *
2127
     * @return string
2128
     *
2129 1
     * @throws Exception
2130
     *
2131 1
     * @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
    public static function decrypt(string $data)
2135
    {
2136
        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
        $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
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
2145
        $plaintext = openssl_decrypt($encrypted_data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
2146
        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
    public static function checkObject($object, $requiredFields): bool
2158
    {
2159
        $haystack = array_keys(json_decode(json_encode($object), true));
2160
        foreach ($requiredFields as $r) {
2161
            if (!in_array($r, $haystack)) {
2162
                return false;
2163
            }
2164
        }
2165
2166
        return true;
2167
    }
2168
2169
    /**
2170
     * Method to retrieve the error data
2171
     *
2172
     * @return string
2173
     */
2174
    public function error(): string
2175
    {
2176
        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
    private function checkParamType($param): int
2190
    {
2191
        // check for implented object interfaces
2192
        $interfaces = is_object($param) ? class_implements($param) : [];
2193
2194
        // numeric is the only datatype we can't have
2195
        if (is_numeric($param)) {
2196
            throw new MissingOrInvalidParam('Numeric parameters are not valid');
2197
        }
2198
2199
        // check for a SELECT statement within an insert
2200
        if (is_string($param) && stripos($param, 'select') !== false) {
2201
            return self::STRING;
2202
        }
2203
        // param is an object, check to see if it includes the required interface
2204
        elseif (is_object($param) && !is_iterable($param)) {
2205
            if (!in_array('Godsgood33\Php_Db\DBInterface', $interfaces)) {
2206
                throw new MissingInterfaceAndMethods('Object does not implement DBInterface interface');
2207
            }
2208
2209
            // check to see if all required methods are callable
2210
            if (!is_callable([$param, 'insert']) ||
2211
                !is_callable([$param, 'update']) ||
2212
                !is_callable([$param, 'replace']) ||
2213
                !is_callable([$param, 'where'])
2214
            ) {
2215
                throw new MissingInterfaceAndMethods("Required DBInterface methods are not present in class ".get_class($param));
2216
            }
2217
2218
            return self::OBJECT;
2219
        }
2220
        // param is an array
2221
        elseif (is_array($param)) {
2222
            // check that there is actual data in the array
2223
            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
            $first = array_shift($param);
2229
            if (is_object($first) && $this->checkParamType($first) == self::OBJECT) {
2230
                return self::ARRAY_OBJECT;
2231
            }
2232
2233
            return self::ARRAY;
2234
        }
2235
        // object is potentially a collection
2236
        elseif (is_object($param)) {
2237
            // check that collection has required interface
2238
            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
            $it = $param->getIterator();
2244
            $first = $it->current();
2245
2246
            // check that the first element of the collection is a valid object as defined above
2247
            if ($this->checkParamType($first) == self::OBJECT) {
2248
                return self::COLLECTION;
2249
            }
2250
        }
2251
2252
        return 0;
2253
    }
2254
}
2255