Test Failed
Push — master ( a3c585...fb63cc )
by Adam
03:04
created

Database::__construct()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 0
cts 9
cp 0
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 6
nop 7
crap 20
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;
14
    protected $sql;
15
    private $key;
16
    
17
    protected $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|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
        try{
44
            $this->connectToServer($username, $password, $database, $hostname, $port);
45
        }
46
        catch(\Exception $e){
47
            if($backuphost !== false){
48
                $this->connectToServer($username, $password, $database, $backuphost, $port);
49
            }
50
            $this->error($e);
51
        }
52
        if(is_object($cache)){
53
            $this->setCaching($cache);
54
        }
55
    }
56
    
57
    /**
58
     * Closes the PDO database connection when Database object unset
59
     */
60
    public function __destruct(){
61
        $this->closeDatabase();
62
    }
63
    
64
    /**
65
     * Connect to the database using PDO connection
66
     * @param string $username This should be the username for the chosen database
67
     * @param string $password This should be the password for the chosen database 
68
     * @param string $database This should be the database that you wish to connect to
69
     * @param string $hostname The hostname for the database
70
     * @param int $port The port number to connect to the MySQL server
71
     */
72
    protected function connectToServer($username, $password, $database, $hostname, $port = 3306){
73
        if(!$this->db){
74
            $this->database = $database;
75
            $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));
76
        }
77
    }
78
    
79
    /**
80
     * Enables the caching and set the caching object to the one provided
81
     * @param object $caching This should be class of the type of caching you are using
82
     */
83
    public function setCaching($caching){
84
        if(is_object($caching)){
85
            $this->cacheObj = $caching;
86
            $this->cacheEnabled = true;
87
        }
88
        return $this;
89
    }
90
    
91
    /**
92
     * This query function is used for more advanced SQL queries for which non of the other methods fit
93
     * @param string $sql This should be the SQL query which you wish to run
94
     * @return array Returns array of results for the query that has just been run
95
     */
96
    public function query($sql, $variables = array(), $cache = true){
97
        try{
98
            $this->sql = $sql;
99
            $this->query = $this->db->prepare($this->sql);
100
            $this->query->execute($variables);
101
            if(strpos($this->sql, 'SELECT') !== false){
102
                return $this->query->fetchAll(PDO::FETCH_ASSOC);
103
            }
104
        }
105
        catch(\Exception $e){
106
            $this->error($e);
107
        }
108
    }
109
    
110
    /**
111
     * Returns a single record for a select query for the chosen table
112
     * @param string $table This should be the table you wish to select the values from
113
     * @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).
114
     * @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).
115
     * @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')
116
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
117
     * @return array Returns a single table record as the standard array when running SQL queries
118
     */
119
    public function select($table, $where = array(), $fields = '*', $order = array(), $cache = true){
120
        return $this->selectAll($table, $where, $fields, $order, 1, $cache);
121
    }
122
    
123
    /**
124
     * Returns a multidimensional array of the results from the selected table given the given parameters
125
     * @param string $table This should be the table you wish to select the values from
126
     * @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).
127
     * @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).
128
     * @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')
129
     * @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)
130
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
131
     * @return array Returns a multidimensional array with the chosen fields from the table
132
     */
133
    public function selectAll($table, $where = array(), $fields = '*', $order = array(), $limit = 0, $cache = true){        
134
        $this->buildSelectQuery($table, $where, $fields, $order, $limit);
135
        $result = $this->executeQuery($cache);
136
        if(!$result){
137
            if($limit === 1){$result = $this->query->fetch(PDO::FETCH_ASSOC);} // Reduce the memory usage if only one record and increase performance
138
            else{$result = $this->query->fetchAll(PDO::FETCH_ASSOC);}
139
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
140
        }
141
        return $result;
142
    }
143
    
144
    /**
145
     * Returns a single column value for a given query
146
     * @param string $table This should be the table you wish to select the values from
147
     * @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).
148
     * @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).
149
     * @param int $colNum This should be the column number you wish to get (starts at 0)
150
     * @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
151
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
152
     * @return mixed If a result is found will return the value of the colum given else will return false
153
     */
154
    public function fetchColumn($table, $where = array(), $fields = '*', $colNum = 0, $order = array(), $cache = true){
155
        $this->buildSelectQuery($table, $where, $fields, $order, 1);
156
        $result = $this->executeQuery($cache);
157
        if(!$result){
158
            $result = $this->query->fetchColumn(intval($colNum));
159
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
160
        }
161
        return $result;
162
    }
163
    
164
    /**
165
     * Inserts into database using the prepared PDO statements 
166
     * @param string $table This should be the table you wish to insert the values into
167
     * @param array $records This should be the field names and values in the format of array('fieldname' => 'value', 'fieldname2' => 'value2', etc.)
168
     * @return boolean If data is inserted returns true else returns false
169
     */
170
    public function insert($table, $records){
171
        unset($this->prepare);
172
        
173
        $this->sql = sprintf("INSERT INTO `%s` (%s) VALUES (%s);", $table, $this->fields($records, true), implode(', ', $this->prepare));
174
        $this->executeQuery(false);
175
        return $this->numRows() ? true : false;
176
    }
177
    
178
    /**
179
     * Updates values in a database using the provide variables
180
     * @param string $table This should be the table you wish to update the values for
181
     * @param array $records This should be the field names and new values in the format of array('fieldname' => 'newvalue', 'fieldname2' => 'newvalue2', etc.)
182
     * @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).
183
     * @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
184
     * @return boolean Returns true if update is successful else returns false
185
     */
186
    public function update($table, $records, $where = array(), $limit = 0){
187
        $this->sql = sprintf("UPDATE `%s` SET %s %s%s;", $table, $this->fields($records), $this->where($where), $this->limit($limit));
188
        $this->executeQuery(false);
189
        return $this->numRows() ? true : false;
190
    }
191
    
192
    /**
193
     * Deletes records from the given table based on the variables given
194
     * @param string $table This should be the table you wish to delete the records from
195
     * @param array $where This should be an array of for the where statement
196
     * @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
197
     */
198
    public function delete($table, $where, $limit = 0){
199
        $this->sql = sprintf("DELETE FROM `%s` %s%s;", $table, $this->where($where), $this->limit($limit));
200
        $this->executeQuery(false);
201
        return $this->numRows() ? true : false;
202
    }
203
    
204
    /**
205
     * Count the number of return results 
206
     * @param string $table The table you wish to count the result of 
207
     * @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).
208
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
209
     * @return int Returns the number of results
210
     */
211
    public function count($table, $where = array(), $cache = true){
212
        $this->sql = sprintf("SELECT count(*) FROM `%s`%s;", $table, $this->where($where));
213
        $this->key = md5($this->database.$this->sql.serialize($this->values));
214
        
215
        $result = $this->executeQuery($cache);
216
        if(!$result){
217
            $result = $this->query->fetchColumn();
218
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
219
        }
220
        return $result;
221
    }
222
    
223
    /**
224
     * Truncates a given table from the selected database so there are no values in the table
225
     * @param string $table This should be the table you wish to truncate
226
     * @return boolean If the table is emptied returns true else returns false
227
     */
228
    public function truncate($table){
229
        try{
230
            $this->sql = sprintf("TRUNCATE TABLE `%s`", $table);
231
            $this->query = $this->db->exec($this->sql);
232
        }
233
        catch(\Exception $e){
234
            $this->error($e);
235
        }
236
        return $this->query ? true : false;
237
    }
238
    
239
    /**
240
     * Returns the number of rows for the last query sent
241
     * @return int Returns the number of rows for the last query
242
     */
243
    public function numRows(){
244
        if(isset($this->query)){
245
            return $this->query->rowCount();
246
        }
247
        return 0;
248
    }
249
    
250
    /**
251
     * Returns the number of rows for the last query sent (Looks a the numRows() function just added incase of habbit)
252
     * @return int Returns the number of rows for the last query
253
     */
254
    public function rowCount(){
255
        return $this->numRows();
256
    }
257
    
258
    /**
259
     * Returns the ID of the last record last inserted 
260
     * @param string $name This should be the name of the sequence object you wish to retrieve
261
     * @return int|string Returns the last inserted ID of the last insert item if $name is null else returns string with sequenced object
262
     */
263
    public function lastInsertId($name = null) {
264
        return $this->db->lastInsertId($name);
265
    }
266
    
267
    /**
268
     * Returns the index of the given table or tables within the database
269
     * @param string|array $table Table can wither be a standard string with a single table name or an array with multiple table names
270
     * @return array Returns the table index for the selected table as an array 
271
     */
272
    public function fulltextIndex($table){
273
        $fieldlist = array();
274
        if(is_array($table)){
275
            foreach($table as $name){
276
                $fieldlist[$name] = $this->fulltextIndex($name);
277
            }
278
        }else{
279
            try{
280
                $this->sql = sprintf("SHOW INDEX FROM %s;",  $table);
281
                $this->query = $this->db->exec($this->sql);
282
            }
283
            catch(\Exception $e){
284
                $this->error($e);
285
            }
286
            
287
            while($index = $this->query->fetch(PDO::FETCH_ASSOC)){
288
                if($index['Index_type'] == 'FULLTEXT' && $index['Key_name'] == 'fulltext'){
289
                    $fieldlist[] = $index['Column_name'];
290
                }
291
            }
292
        }
293
        return $fieldlist;
294
    }
295
    
296
    /**
297
     * Returns the server version information
298
     */
299
    public function serverVersion(){
300
        return $this->db->getAttribute(PDO::ATTR_SERVER_VERSION);
301
    }
302
    
303
    /**
304
     * Displays the error massage which occurs
305
     * @param \Exception $error This should be an instance of Exception
306
     */
307
    private function error($error){
308
        if($this->logErrors){
309
            $file = $this->logLocation.'db-errors.txt';
310
            $current = file_get_contents($file);
311
            $current .= date('d/m/Y H:i:s')." ERROR: ".$error->getMessage()." on ".$this->sql."\n";
312
            file_put_contents($file, $current);
313
        }
314
        if($this->displayErrors){
315
            die('ERROR: '.$error->getMessage().' on '.$this->sql);
316
        }
317
    }
318
    
319
    /**
320
     * Writes all queries to a log file
321
     */
322
    public function writeQueryToLog(){
323
        $file = $this->logLocation.'queries.txt';
324
        $current = file_get_contents($file);
325
        $current .= "SQL: ".$this->sql.":".serialize($this->values)."\n";
326
        file_put_contents($file, $current);
327
    }
328
    
329
    /**
330
     * Closes the PDO database connection by setting the connection to NULL 
331
     */
332
    public function closeDatabase(){
333
        $this->db = null;
334
    }
335
    
336
    /**
337
     * Build the SQL query but doesn't execute it
338
     * @param string $table This should be the table you wish to select the values from
339
     * @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).
340
     * @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).
341
     * @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
342
     * @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)
343
     */
344
    protected function buildSelectQuery($table, $where = array(), $fields = '*', $order = array(), $limit = 0){
345
        if(is_array($fields)){
346
            $selectfields = array();
347
            foreach($fields as $field => $value){
348
                $selectfields[] = sprintf("`%s`", $value);
349
            }
350
            $fieldList = implode(', ', $selectfields);
351
        }
352
        else{$fieldList = '*';}
353
        
354
        $this->sql = sprintf("SELECT %s FROM `%s`%s%s%s;", $fieldList, $table, $this->where($where), $this->orderBy($order), $this->limit($limit));
355
        $this->key = md5($this->database.$this->sql.serialize($this->values));
356
    }
357
    
358
    /**
359
     * Execute the current query if no cache value is available
360
     * @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 
361
     * @return mixed If a cached value exists will be returned else if cache is not checked and query is executed will not return anything
362
     */
363
    protected function executeQuery($cache = true){
364
        if($this->logQueries){$this->writeQueryToLog();}
365
        if($cache && $this->cacheEnabled && $this->getCache($this->key)){
366
            return $this->cacheValue;
367
        }
368
        try{
369
            $this->query = $this->db->prepare($this->sql);
370
            $this->query->execute($this->values);
371
            unset($this->values);
372
        }
373
        catch(\Exception $e){
374
            $this->error($e);
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