Completed
Push — master ( 0bf88a...aed6c8 )
by Adam
01:44
created

Database::update()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 9

Duplication

Lines 4
Ratio 33.33 %

Importance

Changes 0
Metric Value
dl 4
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 4
nop 4
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
31
    /**
32
     * Connect to database using PDO connection
33
     * @param string $hostname This should be the host of the database e.g. 'localhost'
34
     * @param string $username This should be the username for the chosen database
35
     * @param string $password This should be the password for the chosen database 
36
     * @param string $database This should be the database that you wish to connect to
37
     * @param string|null $backuphost If you have a replication server set up put the hostname or IP address incase the primary server goes down
38
     */
39
    public function __construct($hostname, $username, $password, $database, $backuphost = NULL){
40
        try{
41
            $this->connectToServer($username, $password, $database, $hostname);
42
        }
43
        catch(\Exception $e){
44
            if($backuphost !== NULL){
45
                $this->connectToServer($username, $password, $database, $backuphost);
46
            }
47
            $this->error($e);
48
        }
49
    }
50
    
51
    /**
52
     * Closes the PDO database connection when Database object unset
53
     */
54
    public function __destruct(){
55
        $this->closeDatabase();
56
    }
57
    
58
    /**
59
     * Connect to the database using PDO connection
60
     * @param string $username This should be the username for the chosen database
61
     * @param string $password This should be the password for the chosen database 
62
     * @param string $database This should be the database that you wish to connect to
63
     * @param string $hostname The hostname for the database
64
     */
65
    protected function connectToServer($username, $password, $database, $hostname){
66
        if(!$this->db){
67
            $this->database = $database;
68
            $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));
69
        }
70
    }
71
    
72
    /**
73
     * Enables the caching and set the caching object to the one provided
74
     * @param object $caching This should be class of the type of caching you are using
75
     */
76
    public function setCaching($caching){
77
        if(is_object($caching)){
78
            $this->cacheObj = $caching;
79
            $this->cacheEnabled = true;
80
        }
81
        return $this;
82
    }
83
	
84
    /**
85
     * This outputs the SQL where query based on a given array
86
     * @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))
87
     * @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
88
     */
89
    private function where($where){
90
        if(is_array($where)){
91
            $wherefields = array();
92
            foreach($where as $what => $value){
93
                if(is_array($value)){
94
                    if($value[1] == 'NULL' || $value[1] == 'NOT NULL'){
95
                        $wherefields[] = sprintf("`%s` %s %s", $what, addslashes($value[0]), $value[1]);
96
                    }
97
                    else{
98
                        $wherefields[] = sprintf("`%s` %s ?", $what, addslashes($value[0]));
99
                        $this->values[] = $value[1];
100
                    }
101
                }
102
                else{
103
                    $wherefields[] = sprintf("`%s` = ?", $what);
104
                    $this->values[] = $value;
105
                }
106
            }
107
            if(!empty($wherefields)){
108
                return " WHERE ".implode(' AND ', $wherefields);
109
            }
110
        }
111
        return false;
112
    }
113
    
114
    /**
115
     * Sets the order sting for the SQL query based on an array or string
116
     * @param array|string $order This should be either set to array('fieldname' => 'ASC/DESC') or RAND()
117
     * @return string|false If the SQL query has an valid order by will return a string else returns false
118
     */
119
    private function orderBy($order){
120
        if(is_array($order)){
121
            foreach($order as $fieldorder => $fieldvalue){
122
                return sprintf(" ORDER BY `%s` %s", $fieldorder, strtoupper($fieldvalue));
123
            }
124
        }
125
        elseif($order == 'RAND()'){
126
            return " ORDER BY RAND()";
127
        }
128
        return false;
129
    }
130
    
131
    /**
132
     * Returns the limit SQL for the current query as a string
133
     * @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  
134
     * @return string|false Will return the LIMIT string for the current query if it is valid else returns false
135
     */
136
    private function limit($limit = 0){
137
        if(is_array($limit)){
138
            foreach($limit as $start => $end){
139
                 return " LIMIT ".(int)$start.", ".(int)$end;
140
            }
141
        }
142
        elseif((int)$limit > 0){
143
            return " LIMIT ".(int)$limit;
144
        }
145
        return false;
146
    }
147
    
148
    /**
149
     * This query function is used for more advanced SQL queries for which non of the other methods fit
150
     * @param string $sql This should be the SQL query which you wish to run
151
     * @return array Returns array of results for the query that has just been run
152
     */
153
    public function query($sql, $variables = array(), $cache = true){
154
        try{
155
            $this->sql = $sql;
156
            $this->query = $this->db->prepare($this->sql);
157
            $this->query->execute($variables);
158
            if(strpos($this->sql, 'SELECT') !== false){
159
                return $this->query->fetchAll(PDO::FETCH_ASSOC);
160
            }
161
        }
162
        catch(\Exception $e){
163
            $this->error($e);
164
        }
165
    }
166
    
167
    /**
168
     * Returns a single record for a select query for the chosen table
169
     * @param string $table This should be the table you wish to select the values from
170
     * @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).
171
     * @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).
172
     * @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')
173
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
174
     * @return array Returns a single table record as the standard array when running SQL queries
175
     */
176
    public function select($table, $where = array(), $fields = '*', $order = array(), $cache = true){
177
        return $this->selectAll($table, $where, $fields, $order, 1, $cache);
178
    }
179
    
180
    /**
181
     * Returns a multidimensional array of the results from the selected table given the given parameters
182
     * @param string $table This should be the table you wish to select the values from
183
     * @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).
184
     * @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).
185
     * @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')
186
     * @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)
187
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
188
     * @return array Returns a multidimensional array with the chosen fields from the table
189
     */
190
    public function selectAll($table, $where = array(), $fields = '*', $order = array(), $limit = 0, $cache = true){        
191
        $this->buildSelectQuery($table, $where, $fields, $order, $limit);
192
        $result = $this->executeQuery($cache);
193
        if(!$result){
194
            if($limit === 1){$result = $this->query->fetch(PDO::FETCH_ASSOC);} // Reduce the memory usage if only one record and increase performance
195
            else{$result = $this->query->fetchAll(PDO::FETCH_ASSOC);}
196
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
197
        }
198
        return $result;
199
    }
200
    
201
    /**
202
     * Returns a single column value for a given query
203
     * @param string $table This should be the table you wish to select the values from
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 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).
206
     * @param int $colNum This should be the column number you wish to get (starts at 0)
207
     * @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
208
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
209
     * @return mixed If a result is found will return the value of the colum given else will return false
210
     */
211
    public function fetchColumn($table, $where = array(), $fields = '*', $colNum = 0, $order = array(), $cache = true){
212
        $this->buildSelectQuery($table, $where, $fields, $order, 1);
213
        $result = $this->executeQuery($cache);
214
        if(!$result){
215
            $result = $this->query->fetchColumn(intval($colNum));
216
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
217
        }
218
        return $result;
219
    }
220
    
221
    /**
222
     * Inserts into database using the prepared PDO statements 
223
     * @param string $table This should be the table you wish to insert the values into
224
     * @param array $records This should be the field names and values in the format of array('fieldname' => 'value', 'fieldname2' => 'value2', etc.)
225
     * @return boolean If data is inserted returns true else returns false
226
     */
227
    public function insert($table, $records){
228
        $fields = array();
229
        $prepare = array();
230
        $values = array();
231
        
232 View Code Duplication
        foreach($records as $field => $value){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
233
            $fields[] = sprintf("`%s`", $field);
234
            $prepare[] = '?';
235
            $values[] = $value;
236
        }
237
        
238
        try{
239
            unset($this->values);
240
            $this->sql = sprintf("INSERT INTO `%s` (%s) VALUES (%s);", $table, implode(', ', $fields), implode(', ', $prepare));
241
            if($this->logQueries){$this->writeQueryToLog();}
242
            $this->query = $this->db->prepare($this->sql);
243
            $this->query->execute($values);
244
        }
245
        catch(\Exception $e){
246
            $this->error($e);
247
        }
248
        return $this->numRows() ? true : false;
249
    }
250
    
251
    /**
252
     * Updates values in a database using the provide variables
253
     * @param string $table This should be the table you wish to update the values for
254
     * @param array $records This should be the field names and new values in the format of array('fieldname' => 'newvalue', 'fieldname2' => 'newvalue2', etc.)
255
     * @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).
256
     * @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
257
     * @return boolean Returns true if update is successful else returns false
258
     */
259
    public function update($table, $records, $where = array(), $limit = 0){
260
        unset($this->values);
261
        $fields = array();
262
        
263 View Code Duplication
        foreach($records as $field => $value){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
264
            $fields[] = sprintf("`%s` = ?", $field);
265
            $this->values[] = $value;
266
        }
267
        $this->sql = sprintf("UPDATE `%s` SET %s %s%s;", $table, implode(', ', $fields), $this->where($where), $this->limit($limit));
268
        $this->executeQuery(false);
269
        return $this->numRows() ? true : false;
270
    }
271
    
272
    /**
273
     * Deletes records from the given table based on the variables given
274
     * @param string $table This should be the table you wish to delete the records from
275
     * @param array $where This should be an array of for the where statement
276
     * @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
277
     */
278
    public function delete($table, $where, $limit = 0){
279
        unset($this->values);
280
        $this->sql = sprintf("DELETE FROM `%s` %s%s;", $table, $this->where($where), $this->limit($limit));
281
        $this->executeQuery(false);
282
        return $this->numRows() ? true : false;
283
    }
284
    
285
    /**
286
     * Count the number of return results 
287
     * @param string $table The table you wish to count the result of 
288
     * @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).
289
     * @param boolean $cache If the query should be cached or loaded from cache set to true else set to false
290
     * @return int Returns the number of results
291
     */
292
    public function count($table, $where = array(), $cache = true){
293
        unset($this->values);
294
        $this->sql = sprintf("SELECT count(*) FROM `%s`%s;", $table, $this->where($where));
295
        $this->key = md5($this->database.$this->sql.serialize($this->values));
296
        
297
        $result = $this->executeQuery($cache);
298
        if(!$result){
299
            $result = $this->query->fetchColumn();
300
            if($cache && $this->cacheEnabled){$this->setCache($this->key, $result);}
301
        }
302
        return $result;
303
    }
304
    
305
    /**
306
     * Truncates a given table from the selected database so there are no values in the table
307
     * @param string $table This should be the table you wish to truncate
308
     * @return boolean If the table is emptied returns true else returns false
309
     */
310
    public function truncate($table){
311
        try{
312
            $this->sql = sprintf("TRUNCATE TABLE `%s`", $table);
313
            $this->query = $this->db->exec($this->sql);
314
        }
315
        catch(\Exception $e){
316
            $this->error($e);
317
        }
318
        return $this->query ? true : false;
319
    }
320
    
321
    /**
322
     * Returns the number of rows for the last query sent
323
     * @return int Returns the number of rows for the last query
324
     */
325
    public function numRows(){
326
        if(isset($this->query)){
327
            return $this->query->rowCount();
328
        }
329
        return 0;
330
    }
331
    
332
    /**
333
     * Returns the number of rows for the last query sent (Looks a the numRows() function just added incase of habbit)
334
     * @return int Returns the number of rows for the last query
335
     */
336
    public function rowCount(){
337
        return $this->numRows();
338
    }
339
    
340
    /**
341
     * Returns the ID of the last record last inserted 
342
     * @param string $name This should be the name of the sequence object you wish to retrieve
343
     * @return int|string Returns the last inserted ID of the last insert item if $name is null else returns string with sequenced object
344
     */
345
    public function lastInsertId($name = null) {
346
        return $this->db->lastInsertId($name);
347
    }
348
    
349
    /**
350
     * Returns the index of the given table or tables within the database
351
     * @param string|array $table Table can wither be a standard string with a single table name or an array with multiple table names
352
     * @return array Returns the table index for the selected table as an array 
353
     */
354
    public function fulltextIndex($table){
355
        $fieldlist = array();
356
        if(is_array($table)){
357
            foreach($table as $name){
358
                $fieldlist[$name] = $this->fulltextIndex($name);
359
            }
360
        }else{
361
            try{
362
                $this->sql = sprintf("SHOW INDEX FROM %s;",  $table);
363
                $this->query = $this->db->exec($this->sql);
364
            }
365
            catch(\Exception $e){
366
                $this->error($e);
367
            }
368
            
369
            while($index = $this->query->fetch(PDO::FETCH_ASSOC)){
370
                if($index['Index_type'] == 'FULLTEXT' && $index['Key_name'] == 'fulltext'){
371
                    $fieldlist[] = $index['Column_name'];
372
                }
373
            }
374
        }
375
        return $fieldlist;
376
    }
377
    
378
    /**
379
     * Returns the server version information
380
     */
381
    public function serverVersion(){
382
        return $this->db->getAttribute(PDO::ATTR_SERVER_VERSION);
383
    }
384
    
385
    /**
386
     * Displays the error massage which occurs
387
     * @param \Exception $error This should be an instance of Exception
388
     */
389
    private function error($error){
390
        if($this->logErrors){
391
            $file = $this->logLocation.'db-errors.txt';
392
            $current = file_get_contents($file);
393
            $current .= date('d/m/Y H:i:s')." ERROR: ".$error->getMessage()." on ".$this->sql."\n";
394
            file_put_contents($file, $current);
395
        }
396
        if($this->displayErrors){
397
            die('ERROR: '.$error->getMessage().' on '.$this->sql);
398
        }
399
    }
400
    
401
    /**
402
     * Writes all queries to a log file
403
     */
404
    public function writeQueryToLog(){
405
        $file = $this->logLocation.'queries.txt';
406
        $current = file_get_contents($file);
407
        $current .= "SQL: ".$this->sql.":".serialize($this->values)."\n";
408
        file_put_contents($file, $current);
409
    }
410
    
411
    /**
412
     * Closes the PDO database connection by setting the connection to NULL 
413
     */
414
    public function closeDatabase(){
415
        $this->db = null;
416
    }
417
    
418
    /**
419
     * Build the SQL query but doesn't execute it
420
     * @param string $table This should be the table you wish to select the values from
421
     * @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).
422
     * @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).
423
     * @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
424
     * @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)
425
     */
426
    protected function buildSelectQuery($table, $where = array(), $fields = '*', $order = array(), $limit = 0){
427
        if(is_array($fields)){
428
            $selectfields = array();
429
            foreach($fields as $field => $value){
430
                $selectfields[] = sprintf("`%s`", $value);
431
            }
432
            $fieldList = implode(', ', $selectfields);
433
        }
434
        else{$fieldList = '*';}
435
        
436
        unset($this->values);
437
        $this->sql = sprintf("SELECT %s FROM `%s`%s%s%s;", $fieldList, $table, $this->where($where), $this->orderBy($order), $this->limit($limit));
438
        $this->key = md5($this->database.$this->sql.serialize($this->values));
439
    }
440
    
441
    /**
442
     * Execute the current query if no cache value is available
443
     * @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 
444
     * @return mixed If a cached value exists will be returned else if cache is not checked and query is executed will not return anything
445
     */
446
    protected function executeQuery($cache = true){
447
        if($this->logQueries){$this->writeQueryToLog();}
448
        if($cache && $this->cacheEnabled && $this->getCache($this->key)){
449
            return $this->cacheValue;
450
        }
451
        else{
452
            try{
453
                $this->query = $this->db->prepare($this->sql);
454
                $this->query->execute($this->values);
455
            }
456
            catch(\Exception $e){
457
                $this->error($e);
458
            }
459
        }
460
    }
461
    
462
    /**
463
     * Set the cache with a key and value
464
     * @param string $key The unique key to store the value against
465
     * @param mixed $value The value of the MYSQL query 
466
     */
467
    public function setCache($key, $value){
468
        if($this->cacheEnabled){
469
            $this->cacheObj->save($key, $value);
470
        }
471
    }
472
    
473
    /**
474
     * Get the results for a given key
475
     * @param string $key The unique key to check for stored variables
476
     * @return mixed Returned the cached results from
477
     */
478
    public function getCache($key){
479
        if($this->modified === true || !$this->cacheEnabled){return false;}
480
        else{
481
            $this->cacheValue = $this->cacheObj->fetch($key);
482
            return $this->cacheValue;
483
        }
484
    }
485
    
486
    /**
487
     * Clears the cache
488
     */
489
    public function flushDB(){
490
        $this->cacheObj->deleteAll();
491
    }
492
}
493