Passed
Branch master (5bed06)
by Adam
02:15
created

Database::fields()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 3
eloc 10
nc 3
nop 2
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 1.0.1
11
 */
12
final class Database implements DBInterface{
13
    public $db;
14
    public $sql;
15
    private $key;
16
    
17
    public $logLocation = 'logs'.DIRECTORY_SEPARATOR;
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 = array();
30
    private $prepare = array();
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|null $backuphost If you have a replication server set up put the hostname or IP address incase the primary server goes down
39
     */
40
    public function __construct($hostname, $username, $password, $database, $backuphost = NULL){
41
        try{
42
            $this->connectToServer($username, $password, $database, $hostname);
43
        }
44
        catch(\Exception $e){
45
            if($backuphost !== NULL){
46
                $this->connectToServer($username, $password, $database, $backuphost);
47
            }
48
            $this->error($e);
49
        }
50
    }
51
    
52
    /**
53
     * Closes the PDO database connection when Database object unset
54
     */
55
    public function __destruct(){
56
        $this->closeDatabase();
57
    }
58
    
59
    /**
60
     * Connect to the database using PDO connection
61
     * @param string $username This should be the username for the chosen database
62
     * @param string $password This should be the password for the chosen database 
63
     * @param string $database This should be the database that you wish to connect to
64
     * @param string $hostname The hostname for the database
65
     */
66
    protected function connectToServer($username, $password, $database, $hostname){
67
        if(!$this->db){
68
            $this->database = $database;
69
            $this->db = new PDO('mysql:host='.$hostname.';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));
70
        }
71
    }
72
    
73
    /**
74
     * Enables the caching and set the caching object to the one provided
75
     * @param object $caching This should be class of the type of caching you are using
76
     */
77
    public function setCaching($caching){
78
        if(is_object($caching)){
79
            $this->cacheObj = $caching;
80
            $this->cacheEnabled = true;
81
        }
82
        return $this;
83
    }
84
    
85
    /**
86
     * This query function is used for more advanced SQL queries for which non of the other methods fit
87
     * @param string $sql This should be the SQL query which you wish to run
88
     * @return array Returns array of results for the query that has just been run
89
     */
90
    public function query($sql, $variables = array(), $cache = true){
91
        try{
92
            $this->sql = $sql;
93
            $this->query = $this->db->prepare($this->sql);
94
            $this->query->execute($variables);
95
            if(strpos($this->sql, 'SELECT') !== false){
96
                return $this->query->fetchAll(PDO::FETCH_ASSOC);
97
            }
98
        }
99
        catch(\Exception $e){
100
            $this->error($e);
101
        }
102
    }
103
    
104
    /**
105
     * Returns a single record for a select query for the chosen table
106
     * @param string $table This should be the table you wish to select the values from
107
     * @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).
108
     * @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).
109
     * @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')
110
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
111
     * @return array Returns a single table record as the standard array when running SQL queries
112
     */
113
    public function select($table, $where = array(), $fields = '*', $order = array(), $cache = true){
114
        return $this->selectAll($table, $where, $fields, $order, 1, $cache);
115
    }
116
    
117
    /**
118
     * Returns a multidimensional array of the results from the selected table given the given parameters
119
     * @param string $table This should be the table you wish to select the values from
120
     * @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).
121
     * @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).
122
     * @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')
123
     * @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)
124
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
125
     * @return array Returns a multidimensional array with the chosen fields from the table
126
     */
127
    public function selectAll($table, $where = array(), $fields = '*', $order = array(), $limit = 0, $cache = true){        
128
        $this->buildSelectQuery($table, $where, $fields, $order, $limit);
129
        $result = $this->executeQuery($cache);
130
        if(!$result){
131
            if($limit === 1){$result = $this->query->fetch(PDO::FETCH_ASSOC);} // Reduce the memory usage if only one record and increase performance
132
            else{$result = $this->query->fetchAll(PDO::FETCH_ASSOC);}
133
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
134
        }
135
        return $result;
136
    }
137
    
138
    /**
139
     * Returns a single column value for a given query
140
     * @param string $table This should be the table you wish to select the values from
141
     * @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).
142
     * @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).
143
     * @param int $colNum This should be the column number you wish to get (starts at 0)
144
     * @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
145
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
146
     * @return mixed If a result is found will return the value of the colum given else will return false
147
     */
148
    public function fetchColumn($table, $where = array(), $fields = '*', $colNum = 0, $order = array(), $cache = true){
149
        $this->buildSelectQuery($table, $where, $fields, $order, 1);
150
        $result = $this->executeQuery($cache);
151
        if(!$result){
152
            $result = $this->query->fetchColumn(intval($colNum));
153
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
154
        }
155
        return $result;
156
    }
157
    
158
    /**
159
     * Inserts into database using the prepared PDO statements 
160
     * @param string $table This should be the table you wish to insert the values into
161
     * @param array $records This should be the field names and values in the format of array('fieldname' => 'value', 'fieldname2' => 'value2', etc.)
162
     * @return boolean If data is inserted returns true else returns false
163
     */
164
    public function insert($table, $records){
165
        unset($this->values);
166
        unset($this->prepare);
167
        
168
        $this->sql = sprintf("INSERT INTO `%s` (%s) VALUES (%s);", $table, $this->fields($records, true), implode(', ', $this->prepare));
169
        $this->executeQuery(false);
170
        return $this->numRows() ? true : false;
171
    }
172
    
173
    /**
174
     * Updates values in a database using the provide variables
175
     * @param string $table This should be the table you wish to update the values for
176
     * @param array $records This should be the field names and new values in the format of array('fieldname' => 'newvalue', 'fieldname2' => 'newvalue2', etc.)
177
     * @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).
178
     * @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
179
     * @return boolean Returns true if update is successful else returns false
180
     */
181 View Code Duplication
    public function update($table, $records, $where = array(), $limit = 0){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
182
        unset($this->values);
183
        $this->sql = sprintf("UPDATE `%s` SET %s %s%s;", $table, $this->fields($records), $this->where($where), $this->limit($limit));
184
        $this->executeQuery(false);
185
        return $this->numRows() ? true : false;
186
    }
187
    
188
    /**
189
     * Deletes records from the given table based on the variables given
190
     * @param string $table This should be the table you wish to delete the records from
191
     * @param array $where This should be an array of for the where statement
192
     * @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
193
     */
194 View Code Duplication
    public function delete($table, $where, $limit = 0){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
195
        unset($this->values);
196
        $this->sql = sprintf("DELETE FROM `%s` %s%s;", $table, $this->where($where), $this->limit($limit));
197
        $this->executeQuery(false);
198
        return $this->numRows() ? true : false;
199
    }
200
    
201
    /**
202
     * Count the number of return results 
203
     * @param string $table The table you wish to count the result of 
204
     * @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).
205
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
206
     * @return int Returns the number of results
207
     */
208
    public function count($table, $where = array(), $cache = true){
209
        unset($this->values);
210
        $this->sql = sprintf("SELECT count(*) FROM `%s`%s;", $table, $this->where($where));
211
        $this->key = md5($this->database.$this->sql.serialize($this->values));
212
        
213
        $result = $this->executeQuery($cache);
214
        if(!$result){
215
            $result = $this->query->fetchColumn();
216
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
217
        }
218
        return $result;
219
    }
220
    
221
    /**
222
     * Truncates a given table from the selected database so there are no values in the table
223
     * @param string $table This should be the table you wish to truncate
224
     * @return boolean If the table is emptied returns true else returns false
225
     */
226
    public function truncate($table){
227
        try{
228
            $this->sql = sprintf("TRUNCATE TABLE `%s`", $table);
229
            $this->query = $this->db->exec($this->sql);
230
        }
231
        catch(\Exception $e){
232
            $this->error($e);
233
        }
234
        return $this->query ? true : false;
235
    }
236
    
237
    /**
238
     * Returns the number of rows for the last query sent
239
     * @return int Returns the number of rows for the last query
240
     */
241
    public function numRows(){
242
        if(isset($this->query)){
243
            return $this->query->rowCount();
244
        }
245
        return 0;
246
    }
247
    
248
    /**
249
     * Returns the number of rows for the last query sent (Looks a the numRows() function just added incase of habbit)
250
     * @return int Returns the number of rows for the last query
251
     */
252
    public function rowCount(){
253
        return $this->numRows();
254
    }
255
    
256
    /**
257
     * Returns the ID of the last record last inserted 
258
     * @param string $name This should be the name of the sequence object you wish to retrieve
259
     * @return int|string Returns the last inserted ID of the last insert item if $name is null else returns string with sequenced object
260
     */
261
    public function lastInsertId($name = null) {
262
        return $this->db->lastInsertId($name);
263
    }
264
    
265
    /**
266
     * Returns the index of the given table or tables within the database
267
     * @param string|array $table Table can wither be a standard string with a single table name or an array with multiple table names
268
     * @return array Returns the table index for the selected table as an array 
269
     */
270
    public function fulltextIndex($table){
271
        $fieldlist = array();
272
        if(is_array($table)){
273
            foreach($table as $name){
274
                $fieldlist[$name] = $this->fulltextIndex($name);
275
            }
276
        }else{
277
            try{
278
                $this->sql = sprintf("SHOW INDEX FROM %s;",  $table);
279
                $this->query = $this->db->exec($this->sql);
280
            }
281
            catch(\Exception $e){
282
                $this->error($e);
283
            }
284
            
285
            while($index = $this->query->fetch(PDO::FETCH_ASSOC)){
286
                if($index['Index_type'] == 'FULLTEXT' && $index['Key_name'] == 'fulltext'){
287
                    $fieldlist[] = $index['Column_name'];
288
                }
289
            }
290
        }
291
        return $fieldlist;
292
    }
293
    
294
    /**
295
     * Returns the server version information
296
     */
297
    public function serverVersion(){
298
        return $this->db->getAttribute(PDO::ATTR_SERVER_VERSION);
299
    }
300
    
301
    /**
302
     * Displays the error massage which occurs
303
     * @param \Exception $error This should be an instance of Exception
304
     */
305
    private function error($error){
306
        if($this->logErrors){
307
            $file = $this->logLocation.'db-errors.txt';
308
            $current = file_get_contents($file);
309
            $current .= date('d/m/Y H:i:s')." ERROR: ".$error->getMessage()." on ".$this->sql."\n";
310
            file_put_contents($file, $current);
311
        }
312
        if($this->displayErrors){
313
            die('ERROR: '.$error->getMessage().' on '.$this->sql);
314
        }
315
    }
316
    
317
    /**
318
     * Writes all queries to a log file
319
     */
320
    public function writeQueryToLog(){
321
        $file = $this->logLocation.'queries.txt';
322
        $current = file_get_contents($file);
323
        $current .= "SQL: ".$this->sql.":".serialize($this->values)."\n";
324
        file_put_contents($file, $current);
325
    }
326
    
327
    /**
328
     * Closes the PDO database connection by setting the connection to NULL 
329
     */
330
    public function closeDatabase(){
331
        $this->db = null;
332
    }
333
    
334
    /**
335
     * Build the SQL query but doesn't execute it
336
     * @param string $table This should be the table you wish to select the values from
337
     * @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).
338
     * @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).
339
     * @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
340
     * @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)
341
     */
342
    protected function buildSelectQuery($table, $where = array(), $fields = '*', $order = array(), $limit = 0){
343
        if(is_array($fields)){
344
            $selectfields = array();
345
            foreach($fields as $field => $value){
346
                $selectfields[] = sprintf("`%s`", $value);
347
            }
348
            $fieldList = implode(', ', $selectfields);
349
        }
350
        else{$fieldList = '*';}
351
        
352
        unset($this->values);
353
        $this->sql = sprintf("SELECT %s FROM `%s`%s%s%s;", $fieldList, $table, $this->where($where), $this->orderBy($order), $this->limit($limit));
354
        $this->key = md5($this->database.$this->sql.serialize($this->values));
355
    }
356
    
357
    /**
358
     * Execute the current query if no cache value is available
359
     * @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 
360
     * @return mixed If a cached value exists will be returned else if cache is not checked and query is executed will not return anything
361
     */
362
    protected function executeQuery($cache = true){
363
        if($this->logQueries){$this->writeQueryToLog();}
364
        if($cache && $this->cacheEnabled && $this->getCache($this->key)){
365
            return $this->cacheValue;
366
        }
367
        else{
368
            try{
369
                $this->query = $this->db->prepare($this->sql);
370
                $this->query->execute($this->values);
371
            }
372
            catch(\Exception $e){
373
                $this->error($e);
374
            }
375
        }
376
    }
377
	
378
    /**
379
     * This outputs the SQL where query based on a given array
380
     * @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))
381
     * @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
382
     */
383
    private function where($where){
384
        if(is_array($where)){
385
            $wherefields = array();
386
            foreach($where as $what => $value){
387
                if(is_array($value)){
388
                    if($value[1] == 'NULL' || $value[1] == 'NOT NULL'){
389
                        $wherefields[] = sprintf("`%s` %s %s", $what, addslashes($value[0]), $value[1]);
390
                    }
391
                    else{
392
                        $wherefields[] = sprintf("`%s` %s ?", $what, addslashes($value[0]));
393
                        $this->values[] = $value[1];
394
                    }
395
                }
396
                else{
397
                    $wherefields[] = sprintf("`%s` = ?", $what);
398
                    $this->values[] = $value;
399
                }
400
            }
401
            if(!empty($wherefields)){
402
                return " WHERE ".implode(' AND ', $wherefields);
403
            }
404
        }
405
        return false;
406
    }
407
    
408
    /**
409
     * Sets the order sting for the SQL query based on an array or string
410
     * @param array|string $order This should be either set to array('fieldname' => 'ASC/DESC') or RAND()
411
     * @return string|false If the SQL query has an valid order by will return a string else returns false
412
     */
413
    private function orderBy($order){
414
        if(is_array($order)){
415
            foreach($order as $fieldorder => $fieldvalue){
416
                return sprintf(" ORDER BY `%s` %s", $fieldorder, strtoupper($fieldvalue));
417
            }
418
        }
419
        elseif($order == 'RAND()'){
420
            return " ORDER BY RAND()";
421
        }
422
        return false;
423
    }
424
    
425
    /**
426
     * Build the field list for the query
427
     * @param array $records This should be an array listing all of the fields
428
     * @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
429
     * @return string The fields list will be returned as a string to insert into the SQL query
430
     */
431
    private function fields($records, $insert = false){
432
        $fields = array();
433
        
434
        foreach($records as $field => $value){
435
            if($insert === true){
436
                $fields[] = sprintf("`%s`", $field);
437
                $this->prepare[] = '?';
438
            }
439
            else{
440
                $fields[] = sprintf("`%s` = ?", $field);
441
            }
442
            $this->values[] = $value;
443
        }
444
        return implode(', ', $fields);
445
    }
446
    
447
    /**
448
     * Returns the limit SQL for the current query as a string
449
     * @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  
450
     * @return string|false Will return the LIMIT string for the current query if it is valid else returns false
451
     */
452
    private function limit($limit = 0){
453
        if(is_array($limit)){
454
            foreach($limit as $start => $end){
455
                 return " LIMIT ".(int)$start.", ".(int)$end;
456
            }
457
        }
458
        elseif((int)$limit > 0){
459
            return " LIMIT ".(int)$limit;
460
        }
461
        return false;
462
    }
463
    
464
    
465
    /**
466
     * Set the cache with a key and value
467
     * @param string $key The unique key to store the value against
468
     * @param mixed $value The value of the MYSQL query 
469
     */
470
    public function setCache($key, $value){
471
        if($this->cacheEnabled){
472
            $this->cacheObj->save($key, $value);
473
        }
474
    }
475
    
476
    /**
477
     * Get the results for a given key
478
     * @param string $key The unique key to check for stored variables
479
     * @return mixed Returned the cached results from
480
     */
481
    public function getCache($key){
482
        if($this->modified === true || !$this->cacheEnabled){return false;}
483
        else{
484
            $this->cacheValue = $this->cacheObj->fetch($key);
485
            return $this->cacheValue;
486
        }
487
    }
488
    
489
    /**
490
     * Clears the cache
491
     */
492
    public function flushDB(){
493
        $this->cacheObj->deleteAll();
494
    }
495
}
496