Test Failed
Push — 1.0.0-dev ( 73dca1...204371 )
by nguereza
02:34
created

Database::getDsnValueForDriver()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 18
c 0
b 0
f 0
nc 9
nop 1
dl 0
loc 24
rs 9.0444
1
<?php
2
    defined('ROOT_PATH') || exit('Access denied');
3
    /**
4
     * TNH Framework
5
     *
6
     * A simple PHP framework using HMVC architecture
7
     *
8
     * This content is released under the GNU GPL License (GPL)
9
     *
10
     * Copyright (C) 2017 Tony NGUEREZA
11
     *
12
     * This program is free software; you can redistribute it and/or
13
     * modify it under the terms of the GNU General Public License
14
     * as published by the Free Software Foundation; either version 3
15
     * of the License, or (at your option) any later version.
16
     *
17
     * This program is distributed in the hope that it will be useful,
18
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
     * GNU General Public License for more details.
21
     *
22
     * You should have received a copy of the GNU General Public License
23
     * along with this program; if not, write to the Free Software
24
     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25
     */
26
    class Database extends BaseClass {
27
	
28
        /**
29
         * The PDO instance
30
         * @var object
31
         */
32
        private $pdo = null;
33
    
34
        /**
35
         * The database name used for the application
36
         * @var string
37
         */
38
        private $databaseName = null;
39
	
40
        /**
41
         * The number of rows returned by the last query
42
         * @var int
43
         */
44
        private $numRows = 0;
45
	
46
        /**
47
         * The last insert id for the primary key column that have auto increment or sequence
48
         * @var mixed
49
         */
50
        private $insertId = null;
51
	
52
        /**
53
         * The full SQL query statment after build for each command
54
         * @var string
55
         */
56
        private $query = null;
57
	
58
        /**
59
         * The result returned for the last query
60
         * @var mixed
61
         */
62
        private $result = array();
63
	
64
        /**
65
         * The number of executed query for the current request
66
         * @var int
67
         */
68
        private $queryCount = 0;
69
	
70
        /**
71
         * The default data to be used in the statments query INSERT, UPDATE
72
         * @var array
73
         */
74
        private $data = array();
75
	
76
        /**
77
         * The database configuration
78
         * @var array
79
         */
80
        private $config = array();
81
	
82
        /**
83
         * The cache default time to live in second. 0 means no need to use the cache feature
84
         * @var int
85
         */
86
        private $cacheTtl = 0;
87
88
        /**
89
         * The cache current time to live. 0 means no need to use the cache feature
90
         * @var int
91
         */
92
        private $temporaryCacheTtl = 0;
93
94
        /**
95
         * The DatabaseQueryBuilder instance
96
         * @var object
97
         */
98
        protected $queryBuilder = null;
99
    
100
        /**
101
         * The DatabaseQueryRunner instance
102
         * @var object
103
         */
104
        protected $queryRunner = null;
105
106
        /**
107
         * The DatabaseCache instance
108
         * @var object
109
         */
110
        protected $cacheInstance = null;
111
112
113
    /**
114
     * Construct new instance
115
     * @param array $overwriteConfig the config to overwrite with the config set in database.php
116
     * @param boolean $autoConnect whether to connect to database automatically
117
     */
118
    public function __construct($overwriteConfig = array(), $autoConnect = true) {
119
        parent::__construct();
120
		
121
            //Set DatabaseQueryBuilder instance to use
122
        $this->setDependencyInstanceFromParamOrCreate('queryBuilder', null, 'DatabaseQueryBuilder', 'classes/database');
123
       
124
        //Set DatabaseQueryRunner instance to use
125
        $this->setDependencyInstanceFromParamOrCreate('queryRunner', null, 'DatabaseQueryRunner', 'classes/database');
126
127
        //Set DatabaseCache instance to use
128
        $this->setDependencyInstanceFromParamOrCreate('cacheInstance', null, 'DatabaseCache', 'classes/database');
129
130
        //Set database configuration
131
        $this->setDatabaseConfiguration($overwriteConfig, true, $autoConnect);
132
    }
133
134
    /**
135
     * This is used to connect to database
136
     * @return bool 
137
     */
138
    public function connect() {
139
        $config = $this->getDatabaseConfiguration();
140
        if (!empty($config)) {
141
        try {
142
            $this->pdo = new PDO($this->getDsnValueFromConfig(), $config['username'], $config['password']);
143
            $this->pdo->exec("SET NAMES '" . $config['charset'] . "' COLLATE '" . $config['collation'] . "'");
144
            $this->pdo->exec("SET CHARACTER SET '" . $config['charset'] . "'");
145
            $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
146
      
147
            //update of QueryRunner and QueryBuilder properties
148
            $this->updateQueryBuilderAndRunnerProperties();
149
150
            return is_object($this->pdo);
151
            } catch (PDOException $e) {
152
            $this->logger->fatal($e->getMessage());
153
            show_error('Cannot connect to Database.');
154
            return false;
155
            }
156
        }
157
        return false;
158
    }
159
160
161
    /**
162
     * Return the number of rows returned by the current query
163
     * @return int
164
     */
165
    public function numRows() {
166
        return $this->numRows;
167
    }
168
169
    /**
170
     * Return the last insert id value
171
     * @return mixed
172
     */
173
    public function insertId() {
174
        return $this->insertId;
175
    }
176
177
178
    /**
179
     * Get the result of one record rows returned by the current query
180
     * @param  boolean $returnSQLQueryOrResultType if is boolean and true will return the SQL query string.
181
     * If is string will determine the result type "array" or "object"
182
     * @return mixed       the query SQL string or the record result
183
     */
184
    public function get($returnSQLQueryOrResultType = false) {
185
        $this->queryBuilder->limit(1);
186
        $query = $this->getAll($returnSql = true);
187
        if ($returnSQLQueryOrResultType === true) {
188
        return $query;
189
        } else {
190
        return $this->query($query, false, $returnSQLQueryOrResultType == 'array');
191
        }
192
    }
193
194
    /**
195
     * Get the result of record rows list returned by the current query
196
     * @param  boolean|string $returnSQLQueryOrResultType if is boolean and true will return the SQL query string.
197
     * If is string will determine the result type "array" or "object"
198
     * @return mixed       the query SQL string or the record result
199
     */
200
    public function getAll($returnSQLQueryOrResultType = false) {
201
        $query = $this->queryBuilder->getQuery();
202
        if ($returnSQLQueryOrResultType === true) {
203
            return $query;
204
        }
205
        return $this->query($query, true, $returnSQLQueryOrResultType == 'array');
206
    }
207
208
    /**
209
     * Insert new record in the database
210
     * @param  array   $data   the record data if is empty will use the $this->data array.
211
     * @param  boolean $escape  whether to escape or not the values
212
     * @return mixed          the insert id of the new record or null
213
     */
214
    public function insert($data = array(), $escape = true) {
215
        if (empty($data) && !empty($this->data)) {
216
        //as when using $this->setData() may be the data already escaped
217
        $escape = false;
218
        $data = $this->data;
219
        }
220
        $query = $this->queryBuilder->insert($data, $escape)->getQuery();
221
        $result = $this->query($query);
222
        if ($result) {
223
            $this->insertId = $this->pdo->lastInsertId();
224
            //if the table doesn't have the auto increment field or sequence, the value of 0 will be returned 
225
             $id = $this->insertId;
226
            if (!$id) {
227
                $id = true;
228
            }
229
            return $id;
230
        }
231
        return false;
232
    }
233
234
    /**
235
     * Update record in the database
236
     * @param  array   $data   the record data if is empty will use the $this->data array.
237
     * @param  boolean $escape  whether to escape or not the values
238
     * @return mixed          the update status
239
     */
240
    public function update($data = array(), $escape = true) {
241
        if (empty($data) && !empty($this->data)) {
242
        //as when using $this->setData() may be the data already escaped
243
        $escape = false;
244
        $data = $this->data;
245
        }
246
        $query = $this->queryBuilder->update($data, $escape)->getQuery();
247
        return $this->query($query);
248
    }
249
250
    /**
251
     * Delete the record in database
252
     * @return mixed the delete status
253
     */
254
    public function delete() {
255
            $query = $this->queryBuilder->delete()->getQuery();
256
        return $this->query($query);
257
    }
258
259
    /**
260
     * Set database cache time to live
261
     * @param integer $ttl the cache time to live in second
262
     * @return object        the current instance
263
     */
264
    public function setCache($ttl = 0) {
265
        $this->cacheTtl = $ttl;
266
        $this->temporaryCacheTtl = $ttl;
267
        return $this;
268
    }
269
	
270
    /**
271
     * Enabled cache temporary for the current query not globally   
272
     * @param  integer $ttl the cache time to live in second
273
     * @return object        the current instance
274
     */
275
    public function cached($ttl = 0) {
276
        $this->temporaryCacheTtl = $ttl;
277
        return $this;
278
    }
279
280
    /**
281
     * Escape the data before execute query useful for security.
282
     * @param  mixed $data the data to be escaped
283
     * @param boolean $escaped whether we can do escape of not 
284
     * @return mixed       the data after escaped or the same data if no
285
     * need escaped
286
     */
287
    public function escape($data, $escaped = true) {
288
        $data = trim($data);
289
        if ($escaped) {
290
        return $this->pdo->quote($data);
291
        }
292
        return $data; 
293
    }
294
295
    /**
296
     * Return the number query executed count for the current request
297
     * @return int
298
     */
299
    public function queryCount() {
300
        return $this->queryCount;
301
    }
302
303
    /**
304
     * Return the current query SQL string
305
     * @return string
306
     */
307
    public function getQuery() {
308
        return $this->query;
309
    }
310
311
    /**
312
     * Return the application database name
313
     * @return string
314
     */
315
    public function getDatabaseName() {
316
        return $this->databaseName;
317
    }
318
319
    /**
320
     * Return the PDO instance
321
     * @return object
322
     */
323
    public function getPdo() {
324
        return $this->pdo;
325
    }
326
327
    /**
328
     * Set the PDO instance
329
     * @param object $pdo the pdo object
330
     * @return object Database
331
     */
332
    public function setPdo(PDO $pdo) {
333
        $this->pdo = $pdo;
334
        return $this;
335
    }
336
337
    /**
338
     * Return the DatabaseQueryBuilder instance
339
     * @return object DatabaseQueryBuilder
340
     */
341
    public function getQueryBuilder() {
342
        return $this->queryBuilder;
343
    }
344
345
    /**
346
     * Set the DatabaseQueryBuilder instance
347
     * @param object DatabaseQueryBuilder $queryBuilder the DatabaseQueryBuilder object
348
     */
349
    public function setQueryBuilder(DatabaseQueryBuilder $queryBuilder) {
350
        $this->queryBuilder = $queryBuilder;
351
        return $this;
352
    }
353
354
    /**
355
     * Return the DatabaseCache instance
356
     * @return object DatabaseCache
357
     */
358
    public function getCacheInstance() {
359
        return $this->cacheInstance;
360
    }
361
362
    /**
363
     * Set the DatabaseCache instance
364
     * @param object DatabaseCache $cacheInstance the DatabaseCache object
365
     */
366
    public function setCacheInstance(DatabaseCache $cacheInstance) {
367
        $this->cacheInstance = $cacheInstance;
368
        return $this;
369
    }
370
    
371
    /**
372
     * Return the DatabaseQueryRunner instance
373
     * @return object DatabaseQueryRunner
374
     */
375
    public function getQueryRunner() {
376
        return $this->queryRunner;
377
    }
378
379
    /**
380
     * Set the DatabaseQueryRunner instance
381
     * @param object DatabaseQueryRunner $queryRunner the DatabaseQueryRunner object
382
     */
383
    public function setQueryRunner(DatabaseQueryRunner $queryRunner) {
384
        $this->queryRunner = $queryRunner;
385
        return $this;
386
    }
387
388
    /**
389
     * Return the data to be used for insert, update, etc.
390
     * @return array
391
     */
392
    public function getData() {
393
        return $this->data;
394
    }
395
396
    /**
397
     * Set the data to be used for insert, update, etc.
398
     * @param string|array $key the data key identified
399
     * @param mixed $value the data value
400
     * @param boolean $escape whether to escape or not the $value
401
     * @return object        the current Database instance
402
     */
403
    public function setData($key, $value = null, $escape = true) {
404
        if (is_array($key)) {
405
            foreach ($key as $k => $v) {
406
                $this->setData($k, $v, $escape);
407
            }	
408
        } else {
409
        $this->data[$key] = $this->escape($value, $escape);
410
        }
411
        return $this;
412
    }
413
414
    /**
415
     * Execute an SQL query
416
     * @param  string  $query the query SQL string
417
     * @param  boolean $returnAsList  indicate whether to return all record or just one row 
418
     * @param  boolean $returnAsArray return the result as array or not
419
     * @return mixed         the query result
420
     */
421
    public function query($query, $returnAsList = true, $returnAsArray = false) {
422
        $this->reset();
423
        $this->query = preg_replace('/\s\s+|\t\t+/', ' ', trim($query));
424
        $this->logger->info('Execute SQL query [' . $this->query . ']');
425
426
        $cacheExpire = $this->temporaryCacheTtl;
427
428
        //return to the initial cache time
429
        $this->temporaryCacheTtl = $this->cacheTtl;
430
431
        //the database cache content
432
        $cacheContent = $this->cacheInstance->setQuery($query)
433
                                            ->setReturnType($returnAsList)
434
                                            ->setReturnAsArray($returnAsArray)
435
                                            ->setCacheTtl($cacheExpire)
436
                                            ->getCacheContent();
437
        if (!$cacheContent) {
438
            $this->logger->info('No cache data found for this query or is not a SELECT query, get result from real database');
439
            //count the number of query execution to server
440
            $this->queryCount++;
441
            
442
            $queryResult = $this->queryRunner->setQuery($query)
443
                                             ->setReturnType($returnAsList)
444
                                             ->setReturnAsArray($returnAsArray)
445
                                             ->execute();
446
447
            if (is_object($queryResult)) {
448
                $this->result  = $queryResult->getResult();
449
                $this->numRows = $queryResult->getNumRows();
450
                //save the result into cache
451
                $this->cacheInstance->saveCacheContent($this->result);
452
            }
453
        } else {
454
            $this->logger->info('The result for query [' . $this->query . '] already cached use it');
455
            $this->result = $cacheContent;
456
            $this->numRows = count($this->result);
457
        }
458
        
459
        return $this->result;
460
    }
461
462
    /**
463
     * Setting the database configuration using the configuration file and additional configuration from param
464
     * @param array $overwriteConfig the additional configuration to overwrite with the existing one
465
     * @param boolean $useConfigFile whether to use database configuration file
466
     * @param boolean $autoConnect whether to connect to database after set the configuration
467
     * @return object Database
468
     */
469
    public function setDatabaseConfiguration(array $overwriteConfig = array(), $useConfigFile = true, $autoConnect = false) {
470
        $db = array();
471
        if ($useConfigFile && file_exists(CONFIG_PATH . 'database.php')) {
472
            //here don't use require_once because somewhere user can create database instance directly
473
            require CONFIG_PATH . 'database.php';
474
        }
475
      
476
        //merge with the parameter  
477
        $db = array_merge($db, $overwriteConfig);
478
      
479
        //get the default configuration
480
        $config = $this->getDatabaseDefaultConfiguration();
481
		  
482
        $config = array_merge($config, $db);
483
        //determine the port using the hostname like localhost:3307
484
        //hostname will be "localhost", and port "3307"
485
        $p = explode(':', $config['hostname']);
486
        if (count($p) >= 2) {
487
            $config['hostname'] = $p[0];
488
            $config['port'] = $p[1];
489
            }
490
		
491
            $this->databaseName = $config['database'];
492
            $this->config = $config;
493
            $this->logger->info(
494
                                'The database configuration are listed below: ' 
495
                                . stringfy_vars(array_merge(
496
                                                            $this->config, 
497
                                                            array('password' => string_hidden($this->config['password']))
498
                                                ))
499
                            );
500
        if ($autoConnect) {
501
                //Now connect to the database
502
                $this->connect();
503
            }
504
            return $this;
505
    }
506
507
    /**
508
     * Return the database configuration
509
     * @return array
510
     */
511
    public  function getDatabaseConfiguration() {
512
        return $this->config;
513
    }
514
515
    /**
516
     * Close the connexion
517
     */
518
    public function close() {
519
        $this->pdo = null;
520
    }
521
522
    /**
523
     * Return the database default configuration
524
     * @return array
525
     */
526
    protected function getDatabaseDefaultConfiguration() {
527
        return array(
528
                'driver' => '',
529
                'username' => '',
530
                'password' => '',
531
                'database' => '',
532
                'hostname' => 'localhost',
533
                'charset' => 'utf8',
534
                'collation' => 'utf8_general_ci',
535
                'prefix' => '',
536
                'port' => ''
537
            );
538
    }
539
540
    /**
541
     * Update the DatabaseQueryBuilder and DatabaseQueryRunner properties
542
     * @return void
543
     */
544
    protected function updateQueryBuilderAndRunnerProperties() {
545
        //update queryBuilder with some properties needed
546
        if (is_object($this->queryBuilder)) {
547
        $this->queryBuilder->setDriver($this->config['driver'])
548
                            ->setPrefix($this->config['prefix'])
549
                            ->setPdo($this->pdo);
550
        }
551
552
        //update queryRunner with some properties needed
553
        if (is_object($this->queryRunner)) {
554
        $this->queryRunner->setDriver($this->config['driver'])
555
                            ->setPdo($this->pdo);
556
        }
557
    }
558
	
559
560
    /**
561
     * This method is used to get the PDO DSN string using the configured driver
562
     * @return string|null the DSN string or null if can not find it
563
     */
564
    protected function getDsnValueFromConfig() {
565
        $dsn = null;
566
        $config = $this->getDatabaseConfiguration();
567
        if (!empty($config)) {
568
        $driver = $config['driver'];
569
        $driverDsnMap = array(
570
                                'mysql'  => $this->getDsnValueForDriver('mysql'),
571
                                'pgsql'  => $this->getDsnValueForDriver('pgsql'),
572
                                'sqlite' => $this->getDsnValueForDriver('sqlite'),
573
                                'oracle' => $this->getDsnValueForDriver('oracle')
574
                                );
575
        if (isset($driverDsnMap[$driver])) {
576
            $dsn = $driverDsnMap[$driver];
577
        }
578
        }    
579
        return $dsn;
580
    }
581
582
    /**
583
     * Get the DSN value for the given driver
584
     * @param  string $driver the driver name
585
     * @return string|null         the dsn name
586
     */
587
    protected function getDsnValueForDriver($driver) {
588
        $config = $this->getDatabaseConfiguration();
589
        if (empty($config)) {
590
            return null;
591
        }
592
        $dsn = '';
593
        $port = '';
594
        $driversDsn = array(
595
            'mysql' => $driver . ':host=' . $config['hostname'] . ';%sdbname=' . $config['database'],
596
            'pgsql' => $driver . ':host=' . $config['hostname'] . ';%sdbname=' . $config['database'],
597
            'oracle' => 'oci:dbname=' . $config['hostname'] . '%s/' . $config['database'],
598
            'sqlite' => 'sqlite:' . $config['database']
599
        );
600
        if (!empty($config['port'])) {
601
            if (in_array($driver, array('mysql', 'pgsql'))) {
602
                $port = 'port=' . $config['port'] . ';';
603
            } else if ($driver == 'oracle') {
604
                $port = ':' . $config['port'];
605
            }
606
        }
607
        if (isset($driversDsn[$driver])) {
608
            $dsn = sprintf($driversDsn[$driver], $port);
609
        }
610
        return $dsn;
611
    }
612
	
613
    /**
614
     * Reset the database class attributs to the initail values before each query.
615
     */
616
    private function reset() {
617
        //query builder reset
618
        $this->queryBuilder->reset();
619
        $this->numRows  = 0;
620
        $this->insertId = null;
621
        $this->query    = null;
622
        $this->result   = array();
623
        $this->data     = array();
624
    }
625
626
    /**
627
     * The class destructor
628
     */
629
    public function __destruct() {
630
        $this->pdo = null;
631
    }
632
633
}
634