Test Failed
Push — master ( 2269bf...9a40e7 )
by Adam
02:06
created

Database::executeQuery()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 0
cts 11
cp 0
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 11
nc 8
nop 1
crap 42
1
<?php
2
namespace DBAL;
3
4
use PDO;
5
6
/**
7
 * PDO Database connection class
8
 *
9
 * @author Adam Binnersley <[email protected]>
10
 * @version PDO Database Class
11
 */
12
final class Database implements DBInterface{
13
    protected $db;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $db. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
14
    protected $sql;
15
    private $key;
16
    
17
    protected $logLocation;
18
    public $logErrors = true;
19
    public $logQueries = false;
20
    public $displayErrors = false;
21
    
22
    protected $database;
23
    protected $cacheEnabled = false;
24
    protected $cacheObj;
25
    protected $cacheValue;
26
    protected $modified = false;
27
28
    private $query;
29
    private $values = [];
30
    private $prepare = [];
31
32
    /**
33
     * Connect to database using PDO connection
34
     * @param string $hostname This should be the host of the database e.g. 'localhost'
35
     * @param string $username This should be the username for the chosen database
36
     * @param string $password This should be the password for the chosen database 
37
     * @param string $database This should be the database that you wish to connect to
38
     * @param string|false $backuphost If you have a replication server set up put the hostname or IP address incase the primary server goes down
39
     * @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
40
     * @param int $port This should be the port number of the MySQL database connection
41
     */
42
    public function __construct($hostname, $username, $password, $database, $backuphost = false, $cache = false, $port = 3306){
43
        $this->setLogLocation();
44
        try{
45
            $this->connectToServer($username, $password, $database, $hostname, $port);
46
        }
47
        catch(\Exception $e){
48
            if($backuphost !== false){
49
                $this->connectToServer($username, $password, $database, $backuphost, $port);
50
            }
51
            $this->error($e);
52
        }
53
        if(is_object($cache)){
54
            $this->setCaching($cache);
55
        }
56
    }
57
    
58
    /**
59
     * Closes the PDO database connection when Database object unset
60
     */
61
    public function __destruct(){
62
        $this->closeDatabase();
63
    }
64
    
65
    /**
66
     * Connect to the database using PDO connection
67
     * @param string $username This should be the username for the chosen database
68
     * @param string $password This should be the password for the chosen database 
69
     * @param string $database This should be the database that you wish to connect to
70
     * @param string $hostname The hostname for the database
71
     * @param int $port The port number to connect to the MySQL server
72
     */
73
    protected function connectToServer($username, $password, $database, $hostname, $port = 3306){
74
        if(!$this->db){
75
            $this->database = $database;
76
            $this->db = new PDO('mysql:host='.$hostname.';port='.$port.';dbname='.$database, $username, $password, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, PDO::ATTR_PERSISTENT => true, PDO::ATTR_EMULATE_PREPARES => true));
77
        }
78
    }
79
    
80
    /**
81
     * Enables the caching and set the caching object to the one provided
82
     * @param object $caching This should be class of the type of caching you are using
83
     */
84
    public function setCaching($caching){
85
        if(is_object($caching)){
86
            $this->cacheObj = $caching;
87
            $this->cacheEnabled = true;
88
        }
89
        return $this;
90
    }
91
    
92
    /**
93
     * This query function is used for more advanced SQL queries for which non of the other methods fit
94
     * @param string $sql This should be the SQL query which you wish to run
95
     * @return array Returns array of results for the query that has just been run
96
     */
97
    public function query($sql, $variables = array(), $cache = true){
98
        try{
99
            $this->sql = $sql;
100
            $this->query = $this->db->prepare($this->sql);
101
            $this->query->execute($variables);
102
            if(strpos($this->sql, 'SELECT') !== false){
103
                return $this->query->fetchAll(PDO::FETCH_ASSOC);
104
            }
105
        }
106
        catch(\Exception $e){
107
            $this->error($e);
108
        }
109
    }
110
    
111
    /**
112
     * Returns a single record for a select query for the chosen table
113
     * @param string $table This should be the table you wish to select the values from
114
     * @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).
115
     * @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).
116
     * @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')
117
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
118
     * @return array Returns a single table record as the standard array when running SQL queries
119
     */
120
    public function select($table, $where = array(), $fields = '*', $order = array(), $cache = true){
121
        return $this->selectAll($table, $where, $fields, $order, 1, $cache);
122
    }
123
    
124
    /**
125
     * Returns a multidimensional array of the results from the selected table given the given parameters
126
     * @param string $table This should be the table you wish to select the values from
127
     * @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).
128
     * @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).
129
     * @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')
130
     * @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)
131
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
132
     * @return array Returns a multidimensional array with the chosen fields from the table
133
     */
134
    public function selectAll($table, $where = array(), $fields = '*', $order = array(), $limit = 0, $cache = true){        
135
        $this->buildSelectQuery($table, $where, $fields, $order, $limit);
136
        $result = $this->executeQuery($cache);
137
        if(!$result){
138
            if($limit === 1){$result = $this->query->fetch(PDO::FETCH_ASSOC);} // Reduce the memory usage if only one record and increase performance
139
            else{$result = $this->query->fetchAll(PDO::FETCH_ASSOC);}
140
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
141
        }
142
        return $result ? $result : false;
143
    }
144
    
145
    /**
146
     * Returns a single column value for a given query
147
     * @param string $table This should be the table you wish to select the values from
148
     * @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).
149
     * @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).
150
     * @param int $colNum This should be the column number you wish to get (starts at 0)
151
     * @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
152
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
153
     * @return mixed If a result is found will return the value of the colum given else will return false
154
     */
155
    public function fetchColumn($table, $where = array(), $fields = '*', $colNum = 0, $order = array(), $cache = true){
156
        $this->buildSelectQuery($table, $where, $fields, $order, 1);
157
        $result = $this->executeQuery($cache);
158
        if(!$result){
159
            $result = $this->query->fetchColumn(intval($colNum));
160
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
161
        }
162
        return $result;
163
    }
164
    
165
    /**
166
     * Inserts into database using the prepared PDO statements 
167
     * @param string $table This should be the table you wish to insert the values into
168
     * @param array $records This should be the field names and values in the format of array('fieldname' => 'value', 'fieldname2' => 'value2', etc.)
169
     * @return boolean If data is inserted returns true else returns false
170
     */
171
    public function insert($table, $records){
172
        unset($this->prepare);
173
        
174
        $this->sql = sprintf("INSERT INTO `%s` (%s) VALUES (%s);", $table, $this->fields($records, true), implode(', ', $this->prepare));
175
        $this->executeQuery(false);
176
        return $this->numRows() ? true : false;
177
    }
178
    
179
    /**
180
     * Updates values in a database using the provide variables
181
     * @param string $table This should be the table you wish to update the values for
182
     * @param array $records This should be the field names and new values in the format of array('fieldname' => 'newvalue', 'fieldname2' => 'newvalue2', etc.)
183
     * @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).
184
     * @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
185
     * @return boolean Returns true if update is successful else returns false
186
     */
187
    public function update($table, $records, $where = array(), $limit = 0){
188
        $this->sql = sprintf("UPDATE `%s` SET %s %s%s;", $table, $this->fields($records), $this->where($where), $this->limit($limit));
189
        $this->executeQuery(false);
190
        return $this->numRows() ? true : false;
191
    }
192
    
193
    /**
194
     * Deletes records from the given table based on the variables given
195
     * @param string $table This should be the table you wish to delete the records from
196
     * @param array $where This should be an array of for the where statement
197
     * @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
198
     */
199
    public function delete($table, $where, $limit = 0){
200
        $this->sql = sprintf("DELETE FROM `%s` %s%s;", $table, $this->where($where), $this->limit($limit));
201
        $this->executeQuery(false);
202
        return $this->numRows() ? true : false;
203
    }
204
    
205
    /**
206
     * Count the number of return results 
207
     * @param string $table The table you wish to count the result of 
208
     * @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).
209
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
210
     * @return int Returns the number of results
211
     */
212
    public function count($table, $where = array(), $cache = true){
213
        $this->sql = sprintf("SELECT count(*) FROM `%s`%s;", $table, $this->where($where));
214
        $this->key = md5($this->database.$this->sql.serialize($this->values));
215
        
216
        $result = $this->executeQuery($cache);
217
        if(!$result){
218
            $result = $this->query->fetchColumn();
219
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
220
        }
221
        return $result;
222
    }
223
    
224
    /**
225
     * Truncates a given table from the selected database so there are no values in the table
226
     * @param string $table This should be the table you wish to truncate
227
     * @return boolean If the table is emptied returns true else returns false
228
     */
229
    public function truncate($table){
230
        try{
231
            $this->sql = sprintf("TRUNCATE TABLE `%s`", $table);
232
            $this->query = $this->db->exec($this->sql);
233
        }
234
        catch(\Exception $e){
235
            $this->error($e);
236
        }
237
        return $this->query ? true : false;
238
    }
239
    
240
    /**
241
     * Returns the number of rows for the last query sent
242
     * @return int Returns the number of rows for the last query
243
     */
244
    public function numRows(){
245
        if(isset($this->query)){
246
            return $this->query->rowCount();
247
        }
248
        return 0;
249
    }
250
    
251
    /**
252
     * Returns the number of rows for the last query sent (Looks a the numRows() function just added incase of habbit)
253
     * @return int Returns the number of rows for the last query
254
     */
255
    public function rowCount(){
256
        return $this->numRows();
257
    }
258
    
259
    /**
260
     * Returns the ID of the last record last inserted 
261
     * @param string $name This should be the name of the sequence object you wish to retrieve
262
     * @return int|string Returns the last inserted ID of the last insert item if $name is null else returns string with sequenced object
263
     */
264
    public function lastInsertId($name = null) {
265
        return $this->db->lastInsertId($name);
266
    }
267
    
268
    /**
269
     * Returns the index of the given table or tables within the database
270
     * @param string|array $table Table can wither be a standard string with a single table name or an array with multiple table names
271
     * @return array Returns the table index for the selected table as an array 
272
     */
273
    public function fulltextIndex($table){
274
        $fieldlist = array();
275
        if(is_array($table)){
276
            foreach($table as $name){
277
                $fieldlist[$name] = $this->fulltextIndex($name);
278
            }
279
        }else{
280
            try{
281
                $this->query = $this->db->prepare("SHOW INDEX FROM ?;");
282
                $this->query->execute($table);
283
            }
284
            catch(\Exception $e){
285
                $this->error($e);
286
            }
287
            
288
            while($index = $this->query->fetchAll(PDO::FETCH_ASSOC)){
289
                if($index['Index_type'] == 'FULLTEXT' && $index['Key_name'] == 'fulltext'){
290
                    $fieldlist[] = $index['Column_name'];
291
                }
292
            }
293
        }
294
        return $fieldlist;
295
    }
296
    
297
    /**
298
     * Checks to see if a connection has been made to the server
299
     * @return boolean
300
     */
301
    public function isConnected(){
302
        return is_object($this->db) ? true : false;
303
    }
304
    
305
    /**
306
     * Returns the server version information
307
     */
308
    public function serverVersion(){
309
        return $this->db->getAttribute(PDO::ATTR_SERVER_VERSION);
310
    }
311
    
312
    /**
313
     * Sets the location of the log files
314
     * @param string $location This should be where you wish the logs to be stored
315
     * @return $this
316
     */
317
    public function setLogLocation($location = false){
318
        if($location === false){
319
            $location = getcwd().'logs'.DIRECTORY_SEPARATOR;
320
        }
321
        $this->logLocation = $location;
322
        if (!file_exists($location)) {
323
            mkdir($location, 0777, true);
324
        }
325
        return $this;
326
    }
327
    
328
    /**
329
     * Displays the error massage which occurs
330
     * @param \Exception $error This should be an instance of Exception
331
     */
332
    private function error($error){
333
        if($this->logErrors){
334
            $file = $this->logLocation.'db-errors.txt';
335
            $current = file_get_contents($file);
336
            $current .= date('d/m/Y H:i:s')." ERROR: ".$error->getMessage()." on ".$this->sql."\n";
337
            file_put_contents($file, $current);
338
        }
339
        if($this->displayErrors){
340
            die('ERROR: '.$error->getMessage().' on '.$this->sql);
0 ignored issues
show
Coding Style Compatibility introduced by
The method error() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
341
        }
342
    }
343
    
344
    /**
345
     * Writes all queries to a log file
346
     */
347
    public function writeQueryToLog(){
348
        $file = $this->logLocation.'queries.txt';
349
        $current = file_get_contents($file);
350
        $current .= "SQL: ".$this->sql.":".serialize($this->values)."\n";
351
        file_put_contents($file, $current);
352
    }
353
    
354
    /**
355
     * Closes the PDO database connection by setting the connection to NULL 
356
     */
357
    public function closeDatabase(){
358
        $this->db = null;
359
    }
360
    
361
    /**
362
     * Build the SQL query but doesn't execute it
363
     * @param string $table This should be the table you wish to select the values from
364
     * @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).
365
     * @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).
366
     * @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
367
     * @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)
368
     */
369
    protected function buildSelectQuery($table, $where = array(), $fields = '*', $order = array(), $limit = 0){
370
        if(is_array($fields)){
371
            $selectfields = array();
372
            foreach($fields as $field => $value){
373
                $selectfields[] = sprintf("`%s`", $value);
374
            }
375
            $fieldList = implode(', ', $selectfields);
376
        }
377
        else{$fieldList = '*';}
378
        
379
        $this->sql = sprintf("SELECT %s FROM `%s`%s%s%s;", $fieldList, $table, $this->where($where), $this->orderBy($order), $this->limit($limit));
380
        $this->key = md5($this->database.$this->sql.serialize($this->values));
381
    }
382
    
383
    /**
384
     * Execute the current query if no cache value is available
385
     * @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 
386
     * @return mixed If a cached value exists will be returned else if cache is not checked and query is executed will not return anything
387
     */
388
    protected function executeQuery($cache = true){
389
        if($this->logQueries){$this->writeQueryToLog();}
390
        if($cache && $this->cacheEnabled && $this->getCache($this->key)){
391
            return $this->cacheValue;
392
        }
393
        try{
394
            $this->query = $this->db->prepare($this->sql);
395
            $this->query->execute($this->values);
396
            unset($this->values);
397
            $this->values = [];
398
        }
399
        catch(\Exception $e){
400
            $this->error($e);
401
        }
402
}
403
	
404
    /**
405
     * This outputs the SQL where query based on a given array
406
     * @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))
407
     * @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
408
     */
409
    private function where($where){
410
        if(is_array($where) && !empty($where)){
411
            $wherefields = array();
412
            foreach($where as $what => $value){
413
                if(is_array($value)){
414
                    if($value[1] == 'NULL' || $value[1] == 'NOT NULL'){
415
                        $wherefields[] = sprintf("`%s` %s %s", $what, addslashes($value[0]), $value[1]);
416
                    }
417
                    else{
418
                        $wherefields[] = sprintf("`%s` %s ?", $what, addslashes($value[0]));
419
                        $this->values[] = $value[1];
420
                    }
421
                }
422
                else{
423
                    $wherefields[] = sprintf("`%s` = ?", $what);
424
                    $this->values[] = $value;
425
                }
426
            }
427
            if(!empty($wherefields)){
428
                return " WHERE ".implode(' AND ', $wherefields);
429
            }
430
        }
431
        return false;
432
    }
433
    
434
    /**
435
     * Sets the order sting for the SQL query based on an array or string
436
     * @param array|string $order This should be either set to array('fieldname' => 'ASC/DESC') or RAND()
437
     * @return string|false If the SQL query has an valid order by will return a string else returns false
438
     */
439
    private function orderBy($order){
440
        if(is_array($order) && !empty(array_filter($order))){
441
            foreach($order as $fieldorder => $fieldvalue){
442
                return sprintf(" ORDER BY `%s` %s", $fieldorder, strtoupper($fieldvalue));
443
            }
444
        }
445
        elseif($order == 'RAND()'){
446
            return " ORDER BY RAND()";
447
        }
448
        return false;
449
    }
450
    
451
    /**
452
     * Build the field list for the query
453
     * @param array $records This should be an array listing all of the fields
454
     * @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
455
     * @return string The fields list will be returned as a string to insert into the SQL query
456
     */
457
    private function fields($records, $insert = false){
458
        $fields = array();
459
        
460
        foreach($records as $field => $value){
461
            if($insert === true){
462
                $fields[] = sprintf("`%s`", $field);
463
                $this->prepare[] = '?';
464
            }
465
            else{
466
                $fields[] = sprintf("`%s` = ?", $field);
467
            }
468
            $this->values[] = $value;
469
        }
470
        return implode(', ', $fields);
471
    }
472
    
473
    /**
474
     * Returns the limit SQL for the current query as a string
475
     * @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  
476
     * @return string|false Will return the LIMIT string for the current query if it is valid else returns false
477
     */
478
    private function limit($limit = 0){
479
        if(is_array($limit) && !empty(array_filter($limit))){
480
            foreach($limit as $start => $end){
481
                 return " LIMIT ".(int)$start.", ".(int)$end;
482
            }
483
        }
484
        elseif((int)$limit > 0){
485
            return " LIMIT ".(int)$limit;
486
        }
487
        return false;
488
    }
489
    
490
    
491
    /**
492
     * Set the cache with a key and value
493
     * @param string $key The unique key to store the value against
494
     * @param mixed $value The value of the MYSQL query 
495
     */
496
    public function setCache($key, $value){
497
        if($this->cacheEnabled){
498
            $this->cacheObj->save($key, $value);
499
        }
500
    }
501
    
502
    /**
503
     * Get the results for a given key
504
     * @param string $key The unique key to check for stored variables
505
     * @return mixed Returned the cached results from
506
     */
507
    public function getCache($key){
508
        if($this->modified === true || !$this->cacheEnabled){return false;}
509
        else{
510
            $this->cacheValue = $this->cacheObj->fetch($key);
511
            return $this->cacheValue;
512
        }
513
    }
514
    
515
    /**
516
     * Clears the cache
517
     */
518
    public function flushDB(){
519
        $this->cacheObj->deleteAll();
520
    }
521
}
522