Test Failed
Push — master ( 6e1699...1c2f7f )
by Adam
03:10
created

Database::formatValues()   C

Complexity

Conditions 9
Paths 7

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 30.5236

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 5
cts 14
cp 0.357
rs 6.412
c 0
b 0
f 0
cc 9
eloc 15
nc 7
nop 2
crap 30.5236
1
<?php
2
namespace DBAL;
3
4
use PDO;
5
use DBAL\Modifiers\SafeString;
6
use DBAL\Modifiers\Operators;
7
8
/**
9
 * PDO Database connection class
10
 *
11
 * @author Adam Binnersley <[email protected]>
12
 * @version PDO Database Class
13
 */
14
final class Database implements DBInterface{
15
    protected $db;
16
    public $sql;
17
    private $key;
18
    
19
    protected $logLocation;
20
    public $logErrors = true;
21
    public $logQueries = false;
22
    public $displayErrors = false;
23
    
24
    protected $database;
25
    protected $cacheEnabled = false;
26
    protected $cacheObj;
27
    protected $cacheValue;
28
    protected $modified = false;
29
30
    private $query;
31
    private $values = [];
32
    private $prepare = [];
33
    
34
    private static $connectors = array(
35
        'cubrid' => 'cubrid:host=%s;port=%d;dbname=%s',
36
        'dblib' => 'dblib:host=%s:%d;dbname=%s',
37
        'mssql' => 'sqlsrv:Server=%s,%d;Database=%s',
38
        'mysql' => 'mysql:host=%s;port=%d;dbname=%s',
39
        'pgsql' => 'pgsql:host=%s;port=%d;dbname=%s',
40
        'sqlite' => 'sqlite::memory:'
41
    );
42
43
    /**
44
     * Connect to database using PDO connection
45
     * @param string $hostname This should be the host of the database e.g. 'localhost'
46
     * @param string $username This should be the username for the chosen database
47
     * @param string $password This should be the password for the chosen database 
48
     * @param string $database This should be the database that you wish to connect to
49
     * @param string|false $backuphost If you have a replication server set up put the hostname or IP address incase the primary server goes down
50
     * @param object|false $cache If you want to cache the queries with Memcache(d)/Redis/APC/Xcache This should be the object else set to false
51
     * @param boolean $persistent If you want a persistent database connection set to true
52
     * @param string $type The type of connection that you wish to make can be 'mysql', 'cubrid', 'dblib', 'mssql', 'odbc', 'pgsql, or 'sqlite'
53
     * @param int $port This should be the port number of the MySQL database connection
54
     */
55 1
    public function __construct($hostname, $username, $password, $database, $backuphost = false, $cache = false, $persistent = false, $type = 'mysql', $port = 3306) {
56 1
        $this->setLogLocation();
57
        try{
58 1
            $this->connectToServer($username, $password, $database, $hostname, $persistent, $type, $port);
59
        }
60 1
        catch(\Exception $e) {
61 1
            if($backuphost !== false) {
62
                $this->connectToServer($username, $password, $database, $backuphost, $persistent, $type, $port);
63
            }
64 1
            $this->error($e);
65
        }
66 1
        if(is_object($cache)) {
67
            $this->setCaching($cache);
68
        }
69 1
    }
70
    
71
    /**
72
     * Closes the PDO database connection when Database object unset
73
     */
74 1
    public function __destruct() {
75 1
        $this->closeDatabase();
76 1
    }
77
    
78
    /**
79
     * Connect to the database using PDO connection
80
     * @param string $username This should be the username for the chosen database
81
     * @param string $password This should be the password for the chosen database 
82
     * @param string $database This should be the database that you wish to connect to
83
     * @param string $hostname The hostname for the database
84
     * @param boolean $persistent If you want a persistent database connection set to true
85
     * @param string $type The type of connection that you wish to make can be 'mysql', 'cubrid', 'dblib', 'mssql', 'pgsql, or 'sqlite'
86
     * @param int $port The port number to connect to the MySQL server
87
     */
88 2
    protected function connectToServer($username, $password, $database, $hostname, $persistent = false, $type = 'mysql', $port = 3306) {
89 2
        if(!$this->db) {
90 2
            $this->database = $database;
91 2
            $this->db = new PDO(sprintf(self::$connectors[$type], $hostname, $port, $database), $username, $password,
92 2
                array_merge(
93 2
                    ($persistent !== false ? array(PDO::ATTR_PERSISTENT => true) : array()),
94 2
                    ($type === 'mysql' ? array(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, PDO::ATTR_EMULATE_PREPARES => true) : array())
95
                )
96
            );
97 2
            $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
98
        }
99 2
    }
100
    
101
    /**
102
     * Enables the caching and set the caching object to the one provided
103
     * @param object $caching This should be class of the type of caching you are using
104
     */
105 1
    public function setCaching($caching) {
106 1
        if(is_object($caching)) {
107 1
            $this->cacheObj = $caching;
108 1
            $this->cacheEnabled = true;
109
        }
110 1
        return $this;
111
    }
112
    
113
    /**
114
     * This query function is used for more advanced SQL queries for which non of the other methods fit
115
     * @param string $sql This should be the SQL query which you wish to run
116
     * @param array $variables This should be an array of values to execute as the values in a prepared statement
117
     * @return array Returns array of results for the query that has just been run
118
     */
119 1
    public function query($sql, $variables = array(), $cache = true) {
120 1
        if(!empty(trim($sql))){
121
            try{
122 1
                $this->sql = $sql;
123 1
                $this->query = $this->db->prepare($this->sql);
124 1
                $this->query->execute($variables);
125 1
                if(strpos($this->sql, 'SELECT') !== false) {
126 1
                    return $this->query->fetchAll(PDO::FETCH_ASSOC);
127
                }
128
            }
129
            catch(\Exception $e) {
130
                $this->error($e);
131
            }
132
        }
133 1
    }
134
    
135
    /**
136
     * Returns a single record for a select query for the chosen table
137
     * @param string $table This should be the table you wish to select the values from
138
     * @param array $where Should be the field names and values you wish to use as the where query e.g. array('fieldname' => 'value', 'fieldname2' => 'value2', etc).
139
     * @param string|array $fields This should be the records you wis to select from the table. It should be either set as '*' which is the default or set as an array in the following format array('field', 'field2', 'field3', etc).
140
     * @param array $order This is the order you wish the results to be ordered in should be formatted as follows array('fieldname' => 'ASC') or array("'fieldname', 'fieldname2'" => 'DESC')
141
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
142
     * @return array Returns a single table record as the standard array when running SQL queries
143
     */
144
    public function select($table, $where = array(), $fields = '*', $order = array(), $cache = true) {
145
        return $this->selectAll($table, $where, $fields, $order, 1, $cache);
146
    }
147
    
148
    /**
149
     * Returns a multidimensional array of the results from the selected table given the given parameters
150
     * @param string $table This should be the table you wish to select the values from
151
     * @param array $where Should be the field names and values you wish to use as the where query e.g. array('fieldname' => 'value', 'fieldname2' => 'value2', etc).
152
     * @param string|array $fields This should be the records you wis to select from the table. It should be either set as '*' which is the default or set as an array in the following format array('field', 'field2', 'field3', etc).
153
     * @param array $order This is the order you wish the results to be ordered in should be formatted as follows array('fieldname' => 'ASC') or array("'fieldname', 'fieldname2'" => 'DESC')
154
     * @param integer|array $limit The number of results you want to return 0 is default and returns all results, else should be formated either as a standard integer or as an array as the start and end values e.g. array(0 => 150)
155
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
156
     * @return array Returns a multidimensional array with the chosen fields from the table
157
     */
158 2
    public function selectAll($table, $where = array(), $fields = '*', $order = array(), $limit = 0, $cache = true) {        
159 2
        $this->buildSelectQuery(SafeString::makeSafe($table), $where, $fields, $order, $limit);
160 2
        $result = $this->executeQuery($cache);
161 2
        if(!$result) {
162 2
            if($limit === 1) {$result = $this->query->fetch(PDO::FETCH_ASSOC);} // Reduce the memory usage if only one record and increase performance
163 2
            else{$result = $this->query->fetchAll(PDO::FETCH_ASSOC);}
164 2
            if($cache && $this->cacheEnabled) {$this->setCache($this->key, $result);}
165
        }
166 2
        return $result ? $result : false;
167
    }
168
    
169
    /**
170
     * Returns a single column value for a given query
171
     * @param string $table This should be the table you wish to select the values from
172
     * @param array $where Should be the field names and values you wish to use as the where query e.g. array('fieldname' => 'value', 'fieldname2' => 'value2', etc).
173
     * @param array $fields This should be the records you wis to select from the table. It should be either set as '*' which is the default or set as an array in the following format array('field', 'field2', 'field3', etc).
174
     * @param int $colNum This should be the column number you wish to get (starts at 0)
175
     * @param array $order This is the order you wish the results to be ordered in should be formatted as follows array('fieldname' => 'ASC') or array("'fieldname', 'fieldname2'" => 'DESC') so it can be done in both directions
176
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
177
     * @return mixed If a result is found will return the value of the colum given else will return false
178
     */
179 2
    public function fetchColumn($table, $where = array(), $fields = '*', $colNum = 0, $order = array(), $cache = true) {
180 2
        $this->buildSelectQuery(SafeString::makeSafe($table), $where, $fields, $order, 1);
181 2
        $result = $this->executeQuery($cache);
182 2
        if(!$result) {
183 2
            $column = $this->query->fetchColumn(intval($colNum));
184 2
            if($cache && $this->cacheEnabled) {$this->setCache($this->key, $column);}
185 2
            return ($column ? $column : false);
186
        }
187
        return false;
188
    }
189
    
190
    /**
191
     * Inserts into database using the prepared PDO statements 
192
     * @param string $table This should be the table you wish to insert the values into
193
     * @param array $records This should be the field names and values in the format of array('fieldname' => 'value', 'fieldname2' => 'value2', etc.)
194
     * @return boolean If data is inserted returns true else returns false
195
     */
196 2
    public function insert($table, $records) {
197 2
        unset($this->prepare);
198
        
199 2
        $this->sql = sprintf("INSERT INTO `%s` (%s) VALUES (%s);", SafeString::makeSafe($table), $this->fields($records, true), implode(', ', $this->prepare));
200 2
        $this->executeQuery(false);
201 2
        return $this->numRows() ? true : false;
202
    }
203
    
204
    /**
205
     * Updates values in a database using the provide variables
206
     * @param string $table This should be the table you wish to update the values for
207
     * @param array $records This should be the field names and new values in the format of array('fieldname' => 'newvalue', 'fieldname2' => 'newvalue2', etc.)
208
     * @param array $where Should be the field names and values you wish to update in the form of an array e.g. array('fieldname' => 'value', 'fieldname2' => 'value2', etc).
209
     * @param int $limit The number of results you want to return 0 is default and will update all results that match the query, else should be formated as a standard integer
210
     * @return boolean Returns true if update is successful else returns false
211
     */
212 2
    public function update($table, $records, $where = array(), $limit = 0) {
213 2
        $this->sql = sprintf("UPDATE `%s` SET %s %s%s;", SafeString::makeSafe($table), $this->fields($records), $this->where($where), $this->limit($limit));
214 2
        $this->executeQuery(false);
215 2
        return $this->numRows() ? true : false;
216
    }
217
    
218
    /**
219
     * Deletes records from the given table based on the variables given
220
     * @param string $table This should be the table you wish to delete the records from
221
     * @param array $where This should be an array of for the where statement
222
     * @param int $limit The number of results you want to return 0 is default and will delete all results that match the query, else should be formated as a standard integer
223
     */
224 1
    public function delete($table, $where, $limit = 0) {
225 1
        $this->sql = sprintf("DELETE FROM `%s` %s%s;", SafeString::makeSafe($table), $this->where($where), $this->limit($limit));
226 1
        $this->executeQuery(false);
227 1
        return $this->numRows() ? true : false;
228
    }
229
    
230
    /**
231
     * Count the number of return results 
232
     * @param string $table The table you wish to count the result of 
233
     * @param array $where Should be the field names and values you wish to use as the where query e.g. array('fieldname' => 'value', 'fieldname2' => 'value2', etc).
234
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
235
     * @return int Returns the number of results
236
     */
237 1
    public function count($table, $where = array(), $cache = true) {
238 1
        $this->sql = sprintf("SELECT count(*) FROM `%s`%s;", SafeString::makeSafe($table), $this->where($where));
239 1
        $this->key = md5($this->database.$this->sql.serialize($this->values));
240
        
241 1
        $result = $this->executeQuery($cache);
242 1
        if(!$result) {
243 1
            $result = $this->query->fetchColumn();
244 1
            if($cache && $this->cacheEnabled) {$this->setCache($this->key, $result);}
245
        }
246 1
        return $result;
247
    }
248
    
249
    /**
250
     * Truncates a given table from the selected database so there are no values in the table
251
     * @param string $table This should be the table you wish to truncate
252
     * @return boolean If the table is emptied returns true else returns false
253
     */
254 1
    public function truncate($table) {
255
        try{
256 1
            $this->sql = sprintf("TRUNCATE TABLE `%s`", SafeString::makeSafe($table));
257 1
            $this->executeQuery(false);
258
        }
259
        catch(\Exception $e) {
260
            $this->error($e);
261
        }
262 1
        return $this->numRows() ? true : false;
263
    }
264
    
265
    /**
266
     * Returns the number of rows for the last query sent
267
     * @return int Returns the number of rows for the last query
268
     */
269 7
    public function numRows() {
270 7
        if(isset($this->query)) {
271 7
            return $this->query->rowCount();
272
        }
273
        return 0;
274
    }
275
    
276
    /**
277
     * Returns the number of rows for the last query sent (Looks a the numRows() function just added incase of habbit)
278
     * @return int Returns the number of rows for the last query
279
     */
280 1
    public function rowCount() {
281 1
        return $this->numRows();
282
    }
283
    
284
    /**
285
     * Returns the ID of the last record last inserted 
286
     * @param string $name This should be the name of the sequence object you wish to retrieve
287
     * @return int|string Returns the last inserted ID of the last insert item if $name is null else returns string with sequenced object
288
     */
289 1
    public function lastInsertId($name = null) {
290 1
        return $this->db->lastInsertId($name);
291
    }
292
    
293
    /**
294
     * Checks to see if a connection has been made to the server
295
     * @return boolean
296
     */
297 2
    public function isConnected() {
298 2
        return is_object($this->db) ? true : false;
299
    }
300
    
301
    /**
302
     * Returns the server version information
303
     */
304 1
    public function serverVersion() {
305 1
        return $this->db->getAttribute(PDO::ATTR_SERVER_VERSION);
306
    }
307
    
308
    /**
309
     * Sets the location of the log files
310
     * @param string $location This should be where you wish the logs to be stored
311
     * @return $this
312
     */
313 1
    public function setLogLocation($location = false) {
314 1
        if($location === false) {
315 1
            $location = dirname(__FILE__).DIRECTORY_SEPARATOR.'logs'.DIRECTORY_SEPARATOR;
316
        }
317 1
        $this->logLocation = $location;
318 1
        if (!file_exists($location)) {
319 1
            mkdir($location, 0777, true);
320
        }
321 1
        return $this;
322
    }
323
    
324
    /**
325
     * Displays the error massage which occurs
326
     * @param \Exception $error This should be an instance of Exception
327
     */
328 4
    private function error($error) {
329 4
        if($this->logErrors) {
330 4
            $file = $this->logLocation.'db-errors.txt';
331 4
            $current = file_get_contents($file);
332 4
            $current .= date('d/m/Y H:i:s')." ERROR: ".$error->getMessage()." on ".$this->sql."\n";
333 4
            file_put_contents($file, $current);
334
        }
335 4
        if($this->displayErrors) {
336
            die('ERROR: '.$error->getMessage().' on '.$this->sql);
337
        }
338 4
    }
339
    
340
    /**
341
     * Writes all queries to a log file
342
     */
343
    public function writeQueryToLog() {
344
        $file = $this->logLocation.'queries.txt';
345
        $current = file_get_contents($file);
346
        $current .= "SQL: ".$this->sql.":".serialize($this->values)."\n";
347
        file_put_contents($file, $current);
348
    }
349
    
350
    /**
351
     * Closes the PDO database connection by setting the connection to NULL 
352
     */
353 1
    public function closeDatabase() {
354 1
        $this->db = null;
355 1
    }
356
    
357
    /**
358
     * Build the SQL query but doesn't execute it
359
     * @param string $table This should be the table you wish to select the values from
360
     * @param array $where Should be the field names and values you wish to use as the where query e.g. array('fieldname' => 'value', 'fieldname2' => 'value2', etc).
361
     * @param string|array $fields This should be the records you wis to select from the table. It should be either set as '*' which is the default or set as an array in the following format array('field', 'field2', 'field3', etc).
362
     * @param array $order This is the order you wish the results to be ordered in should be formatted as follows array('fieldname' => 'ASC') or array("'fieldname', 'fieldname2'" => 'DESC') so it can be done in both directions
363
     * @param integer|array $limit The number of results you want to return 0 is default and returns all results, else should be formated either as a standard integer or as an array as the start and end values e.g. array(0 => 150)
364
     */
365 4
    protected function buildSelectQuery($table, $where = array(), $fields = '*', $order = array(), $limit = 0) {
366 4
        if(is_array($fields)) {
367
            $selectfields = array();
368
            foreach($fields as $field => $value) {
369
                $selectfields[] = sprintf("`%s`", SafeString::makeSafe($value));
370
            }
371
            $fieldList = implode(', ', $selectfields);
372
        }
373 4
        else{$fieldList = '*';}
374
        
375 4
        $this->sql = sprintf("SELECT %s FROM `%s`%s%s%s;", $fieldList, SafeString::makeSafe($table), $this->where($where), $this->orderBy($order), $this->limit($limit));
376 4
        $this->key = md5($this->database.$this->sql.serialize($this->values));
377 4
    }
378
    
379
    /**
380
     * Execute the current query if no cache value is available
381
     * @param boolean $cache If the cache should be checked for the checked for the values of the query set to true else set to false 
382
     * @return mixed If a cached value exists will be returned else if cache is not checked and query is executed will not return anything
383
     */
384 11
    protected function executeQuery($cache = true) {
385 11
        if($this->logQueries) {$this->writeQueryToLog();}
386 11
        if($cache && $this->cacheEnabled && $this->getCache($this->key)) {
387
            return $this->cacheValue;
388
        }
389
        try{
390 11
            $this->query = $this->db->prepare($this->sql);
391 11
            $this->bindValues($this->values);
392 11
            $this->query->execute();
393 11
            unset($this->values);
394 11
            $this->values = [];
395
        }
396 3
        catch(\Exception $e) {
397 3
            unset($this->values);
398 3
            $this->values = [];
399 3
            $this->error($e);
400
        }
401 11
    }
402
	
403
    /**
404
     * This outputs the SQL where query based on a given array
405
     * @param array $where This should be an array that you wish to create the where query for in the for array('field1' => 'test') or array('field1' => array('>=', 0))
406
     * @return string|false If the where query is an array will return the where string and set the values else returns false if no array sent
407
     */
408 7
    private function where($where) {
409 7
        if(is_array($where) && !empty($where)) {
410 6
            $wherefields = array();
411 6
            foreach($where as $field => $value) {
412 6
                $wherefields[] = $this->formatValues($field, $value);
413
            }
414 6
            if(!empty($wherefields)) {
415 6
                return " WHERE ".implode(' AND ', $wherefields);
416
            }
417
        }
418 2
        return false;
419
    }
420
    
421
    /**
422
     * Sets the order sting for the SQL query based on an array or string
423
     * @param array|string $order This should be either set to array('fieldname' => 'ASC/DESC') or RAND()
424
     * @return string|false If the SQL query has an valid order by will return a string else returns false
425
     */
426
    private function orderBy($order) {
427
        if(is_array($order) && !empty(array_filter($order))) {
428
            $string = array();
429
            foreach($order as $fieldorder => $fieldvalue) {
430
                if(!empty($fieldorder) && !empty($fieldvalue)) {
431
                    $string[] = sprintf("`%s` %s", SafeString::makeSafe($fieldorder), strtoupper(SafeString::makeSafe($fieldvalue)));
432
                }
433
                elseif($fieldvalue === 'RAND()') {
434
                    $string[] = $fieldvalue;
435
                }
436
            }
437
            return sprintf(" ORDER BY %s", implode(", ", $string));
438
        }
439
        elseif($order == 'RAND()') {
440
            return " ORDER BY RAND()";
441
        }
442
        return false;
443
    }
444
    
445
    /**
446
     * Build the field list for the query
447
     * @param array $records This should be an array listing all of the fields
448
     * @param boolean $insert If this is an insert statement should be set to true to create the correct amount of queries for the prepared statement
449
     * @return string The fields list will be returned as a string to insert into the SQL query
450
     */
451 4
    private function fields($records, $insert = false) {
452 4
        $fields = array();
453
        
454 4
        foreach($records as $field => $value) {
455 4
            if($insert === true) {
456 4
                $fields[] = sprintf("`%s`", SafeString::makeSafe($field));
457 4
                $this->prepare[] = '?';
458
            }
459
            else{
460 2
                $fields[] = sprintf("`%s` = ?", SafeString::makeSafe($field));
461
            }
462 4
            $this->values[] = $value;
463
        }
464 4
        return implode(', ', $fields);
465
    }
466
    
467
    /**
468
     * Returns the limit SQL for the current query as a string
469
     * @param integer|array $limit This should either be set as an integer or should be set as an array with a start and end value  
470
     * @return string|false Will return the LIMIT string for the current query if it is valid else returns false
471
     */
472 1
    private function limit($limit = 0) {
473 1
        if(is_array($limit) && !empty(array_filter($limit))) {
474 1
            foreach($limit as $start => $end) {
475 1
                 return " LIMIT ".intval($start).", ".intval($end);
476
            }
477
        }
478 1
        elseif((int)$limit > 0) {
479 1
            return " LIMIT ".intval($limit);
480
        }
481 1
        return false;
482
    }
483
    
484
    
485
    /**
486
     * Set the cache with a key and value
487
     * @param string $key The unique key to store the value against
488
     * @param mixed $value The value of the MYSQL query 
489
     */
490 2
    public function setCache($key, $value) {
491 2
        if($this->cacheEnabled) {
492 2
            $this->cacheObj->save($key, $value);
493
        }
494 2
    }
495
    
496
    /**
497
     * Get the results for a given key
498
     * @param string $key The unique key to check for stored variables
499
     * @return mixed Returned the cached results from
500
     */
501 2
    public function getCache($key) {
502 2
        if($this->modified === true || !$this->cacheEnabled) {return false;}
503
        else{
504 2
            $this->cacheValue = $this->cacheObj->fetch($key);
505 2
            return $this->cacheValue;
506
        }
507
    }
508
    
509
    /**
510
     * Clears the cache
511
     */
512
    public function flushDB() {
513
        $this->cacheObj->deleteAll();
514
    }
515
    
516
    /**
517
     * Format the where queries and set the prepared values
518
     * @param string $field This should be the field name in the database
519
     * @param mixed $value This should be the value which should either be a string or an array if it contains an operator
520
     * @return string This should be the string to add to the SQL query
521
     */
522 6
    protected function formatValues($field, $value) {
523 6
        if(!is_array($value) && Operators::isOperatorValid($value) && !Operators::isOperatorPrepared($value)) {
524
            return sprintf("`%s` %s", SafeString::makeSafe($field), Operators::getOperatorFormat($value));
525
        }
526 6
        elseif(is_array($value)) {
527
            if(!is_array(array_values($value)[0])) {
528
                $this->values[] = (isset($value[1]) ? $value[1] : array_values($value)[0]);
529
                $operator = (isset($value[0]) ? $value[0] : key($value));
530
            }
531
            else{
532
                foreach(array_values($value)[0] as $op => $array_value) {
533
                    $this->values[] = $array_value;
534
                    $keys[] = '?';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$keys was never initialized. Although not strictly required by PHP, it is generally a good practice to add $keys = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
535
                }
536
                $operator = key($value);
537
            }
538
            return sprintf("`%s` %s", SafeString::makeSafe($field),  sprintf(Operators::getOperatorFormat($operator), implode($keys, ', ')));
0 ignored issues
show
Bug introduced by
The variable $keys does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
539
            
540
        }
541 6
        $this->values[] = $value;
542 6
        return sprintf("`%s` = ?", SafeString::makeSafe($field));
543
    }
544
    
545
    /**
546
     * Band values to use in the query
547
     * @param array $values This should be the values being used in the query
548
     */
549 10
    protected function bindValues($values) {
550 10
        if(is_array($values)) {
551 10
            foreach($values as $i => $value) {
552 10
                if(is_numeric($value) && intval($value) == $value && $value[0] != 0) {$type = PDO::PARAM_INT; $value = intval($value);}
553 10
                elseif(is_null($value) || $value === 'NULL') {$type = PDO::PARAM_NULL; $value = NULL;}
554 10
                elseif(is_bool($value)) {$type = PDO::PARAM_BOOL;}
555 10
                else{$type = PDO::PARAM_STR;}
556 10
                $this->query->bindValue(intval($i + 1), $value, $type);
557
            }
558
        }
559 10
    }
560
}
561