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

Database::extendedReplace()   C

Complexity

Conditions 15
Paths 21

Size

Total Lines 52
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 15.7823

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 15
eloc 32
c 5
b 0
f 0
nc 21
nop 3
dl 0
loc 52
ccs 28
cts 33
cp 0.8485
crap 15.7823
rs 5.9166

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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