Passed
Push — 1.0.0-dev ( 93958a...e1c8ef )
by nguereza
02:26
created

Database::getLogger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 1
Metric Value
cc 1
eloc 1
c 1
b 1
f 1
nc 1
nop 0
dl 0
loc 2
rs 10
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 cache default time to live in second. 0 means no need to use the cache feature
66
  	 * @var int
67
  	*/
68
  	private $cacheTtl             = 0;
69
	
70
  	/**
71
  	 * The cache current time to live. 0 means no need to use the cache feature
72
  	 * @var int
73
  	*/
74
    private $temporaryCacheTtl   = 0;
75
	
76
  	/**
77
  	 * The number of executed query for the current request
78
  	 * @var int
79
  	*/
80
    private $queryCount          = 0;
81
	
82
  	/**
83
  	 * The default data to be used in the statments query INSERT, UPDATE
84
  	 * @var array
85
  	*/
86
    private $data                = array();
87
	
88
  	/**
89
  	 * The database configuration
90
  	 * @var array
91
  	*/
92
    private $config              = array();
93
	
94
    /**
95
    * The cache instance
96
    * @var object
97
    */
98
    private $cacheInstance       = null;
99
100
    
101
  	/**
102
    * The DatabaseQueryBuilder instance
103
    * @var object
104
    */
105
    protected $queryBuilder        = null;
106
    
107
    /**
108
    * The DatabaseQueryRunner instance
109
    * @var object
110
    */
111
    protected $queryRunner         = null;
112
113
114
    /**
115
     * Construct new database
116
     * @param array $overwriteConfig the config to overwrite with the config set in database.php
117
     * @param boolean $autoConnect whether to connect to database automatically
118
     */
119
    public function __construct($overwriteConfig = array(), $autoConnect = true){
120
        parent::__construct();
121
		
122
    		//Set DatabaseQueryBuilder instance to use
123
        $this->setDependencyInstanceFromParamOrCreate('queryBuilder', null, 'DatabaseQueryBuilder', 'classes/database');
124
       
125
        //Set DatabaseQueryRunner instance to use
126
        $this->setDependencyInstanceFromParamOrCreate('queryRunner', null, 'DatabaseQueryRunner', 'classes/database');
127
128
        //Set database configuration
129
        $this->setDatabaseConfiguration($overwriteConfig, true, $autoConnect);
130
        
131
        //cache time to live
132
        $this->temporaryCacheTtl = $this->cacheTtl;
133
    }
134
135
    /**
136
     * This is used to connect to database
137
     * @return bool 
138
     */
139
    public function connect(){
140
      $config = $this->getDatabaseConfiguration();
141
      if (! empty($config)){
142
        try{
143
            $this->pdo = new PDO($this->getDsnValueFromConfig(), $config['username'], $config['password']);
144
            $this->pdo->exec("SET NAMES '" . $config['charset'] . "' COLLATE '" . $config['collation'] . "'");
145
            $this->pdo->exec("SET CHARACTER SET '" . $config['charset'] . "'");
146
            $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
147
      
148
            //update of QueryRunner and QueryBuilder properties
149
            $this->updateQueryBuilderAndRunnerProperties();
150
151
            return is_object($this->pdo);
152
          }
153
          catch (PDOException $e){
154
            $this->logger->fatal($e->getMessage());
155
            show_error('Cannot connect to Database.');
156
            return false;
157
          }
158
      }
159
      return false;
160
    }
161
162
163
    /**
164
     * Return the number of rows returned by the current query
165
     * @return int
166
     */
167
    public function numRows(){
168
      return $this->numRows;
169
    }
170
171
    /**
172
     * Return the last insert id value
173
     * @return mixed
174
     */
175
    public function insertId(){
176
      return $this->insertId;
177
    }
178
179
180
    /**
181
     * Get the result of one record rows returned by the current query
182
     * @param  boolean $returnSQLQueryOrResultType if is boolean and true will return the SQL query string.
183
     * If is string will determine the result type "array" or "object"
184
     * @return mixed       the query SQL string or the record result
185
     */
186
    public function get($returnSQLQueryOrResultType = false){
187
      $this->queryBuilder->limit(1);
188
      $query = $this->getAll(true);
189
      if ($returnSQLQueryOrResultType === true){
190
        return $query;
191
      } else {
192
        return $this->query($query, false, $returnSQLQueryOrResultType == 'array');
193
      }
194
    }
195
196
    /**
197
     * Get the result of record rows list returned by the current query
198
     * @param  boolean|string $returnSQLQueryOrResultType if is boolean and true will return the SQL query string.
199
     * If is string will determine the result type "array" or "object"
200
     * @return mixed       the query SQL string or the record result
201
     */
202
    public function getAll($returnSQLQueryOrResultType = false){
203
	   $query = $this->queryBuilder->getQuery();
204
	   if ($returnSQLQueryOrResultType === true){
205
      	return $query;
206
      }
207
      return $this->query($query, true, $returnSQLQueryOrResultType == 'array');
208
    }
209
210
    /**
211
     * Insert new record in the database
212
     * @param  array   $data   the record data if is empty will use the $this->data array.
213
     * @param  boolean $escape  whether to escape or not the values
214
     * @return mixed          the insert id of the new record or null
215
     */
216
    public function insert($data = array(), $escape = true){
217
      if (empty($data) && ! empty($this->data)){
218
        //as when using $this->setData() may be the data already escaped
219
        $escape = false;
220
        $data = $this->data;
221
      }
222
      $query = $this->queryBuilder->insert($data, $escape)->getQuery();
223
      $result = $this->query($query);
224
      if ($result){
225
        $this->insertId = $this->pdo->lastInsertId();
226
		    //if the table doesn't have the auto increment field or sequence, the value of 0 will be returned 
227
        return ! ($this->insertId) ? true : $this->insertId;
228
      }
229
      return false;
230
    }
231
232
    /**
233
     * Update record in the database
234
     * @param  array   $data   the record data if is empty will use the $this->data array.
235
     * @param  boolean $escape  whether to escape or not the values
236
     * @return mixed          the update status
237
     */
238
    public function update($data = array(), $escape = true){
239
      if (empty($data) && ! empty($this->data)){
240
        //as when using $this->setData() may be the data already escaped
241
        $escape = false;
242
        $data = $this->data;
243
      }
244
      $query = $this->queryBuilder->update($data, $escape)->getQuery();
245
      return $this->query($query);
246
    }
247
248
    /**
249
     * Delete the record in database
250
     * @return mixed the delete status
251
     */
252
    public function delete(){
253
		  $query = $this->queryBuilder->delete()->getQuery();
254
    	return $this->query($query);
255
    }
256
257
    /**
258
     * Set database cache time to live
259
     * @param integer $ttl the cache time to live in second
260
     * @return object        the current Database instance
261
     */
262
    public function setCache($ttl = 0){
263
      $this->cacheTtl = $ttl;
264
      $this->temporaryCacheTtl = $ttl;
265
      return $this;
266
    }
267
	
268
	/**
269
	 * Enabled cache temporary for the current query not globally	
270
	 * @param  integer $ttl the cache time to live in second
271
	 * @return object        the current Database instance
272
	 */
273
  	public function cached($ttl = 0){
274
        $this->temporaryCacheTtl = $ttl;
275
        return $this;
276
    }
277
278
    /**
279
     * Escape the data before execute query useful for security.
280
     * @param  mixed $data the data to be escaped
281
     * @param boolean $escaped whether we can do escape of not 
282
     * @return mixed       the data after escaped or the same data if no
283
     * need escaped
284
     */
285
    public function escape($data, $escaped = true){
286
      $data = trim($data);
287
      if($escaped){
288
        return $this->pdo->quote($data);
289
      }
290
      return $data; 
291
    }
292
293
    /**
294
     * Return the number query executed count for the current request
295
     * @return int
296
     */
297
    public function queryCount(){
298
      return $this->queryCount;
299
    }
300
301
    /**
302
     * Return the current query SQL string
303
     * @return string
304
     */
305
    public function getQuery(){
306
      return $this->query;
307
    }
308
309
    /**
310
     * Return the application database name
311
     * @return string
312
     */
313
    public function getDatabaseName(){
314
      return $this->databaseName;
315
    }
316
317
    /**
318
     * Return the PDO instance
319
     * @return object
320
     */
321
    public function getPdo(){
322
      return $this->pdo;
323
    }
324
325
    /**
326
     * Set the PDO instance
327
     * @param object $pdo the pdo object
328
	 * @return object Database
329
     */
330
    public function setPdo(PDO $pdo){
331
      $this->pdo = $pdo;
332
      return $this;
333
    }
334
335
     /**
336
     * Return the cache instance
337
     * @return CacheInterface
338
     */
339
    public function getCacheInstance(){
340
      return $this->cacheInstance;
341
    }
342
343
    /**
344
     * Set the cache instance
345
     * @param CacheInterface $cache the cache object
346
	 * @return object Database
347
     */
348
    public function setCacheInstance($cache){
349
      $this->cacheInstance = $cache;
350
      return $this;
351
    }
352
	
353
	
354
	   /**
355
     * Return the DatabaseQueryBuilder instance
356
     * @return object DatabaseQueryBuilder
357
     */
358
    public function getQueryBuilder(){
359
      return $this->queryBuilder;
360
    }
361
362
    /**
363
     * Set the DatabaseQueryBuilder instance
364
     * @param object DatabaseQueryBuilder $queryBuilder the DatabaseQueryBuilder object
365
     */
366
    public function setQueryBuilder(DatabaseQueryBuilder $queryBuilder){
367
      $this->queryBuilder = $queryBuilder;
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
      //If is the SELECT query
425
      $isSqlSELECTQuery = stristr($this->query, 'SELECT');
426
427
      //cache expire time
428
      $cacheExpire = $this->temporaryCacheTtl;
429
      
430
      //return to the initial cache time
431
      $this->temporaryCacheTtl = $this->cacheTtl;
432
      
433
      //config for cache
434
      $cacheEnable = get_config('cache_enable');
435
      
436
      //the database cache content
437
      $cacheContent = null;
438
439
      //if can use cache feature for this query
440
      $dbCacheStatus = $cacheEnable && $cacheExpire > 0;
441
    
442
      if ($dbCacheStatus && $isSqlSELECTQuery){
443
          $this->logger->info('The cache is enabled for this query, try to get result from cache'); 
444
          $cacheContent = $this->getCacheContentForQuery($query, $returnAsList, $returnAsArray);  
445
      }
446
      
447
      if (!$cacheContent){
448
  	   	//count the number of query execution to server
449
        $this->queryCount++;
450
        
451
        $queryResult = $this->queryRunner->setQuery($query)
452
                                          ->setReturnType($returnAsList)
453
                                          ->setReturnAsArray($returnAsArray)
454
                                          ->execute();
455
456
        if (!is_object($queryResult)){
457
          $this->result = false;
458
          $this->numRows = 0;
459
          return $this->result;
460
        }
461
        $this->result  = $queryResult->getResult();
462
        $this->numRows = $queryResult->getNumRows();
463
        if ($isSqlSELECTQuery && $dbCacheStatus){
464
            $key = $this->getCacheKeyForQuery($this->query, $returnAsList, $returnAsArray);
465
            $this->setCacheContentForQuery($this->query, $key, $this->result, $cacheExpire);
466
        }
467
      } else if ($isSqlSELECTQuery){
468
          $this->logger->info('The result for query [' .$this->query. '] already cached use it');
469
          $this->result = $cacheContent;
470
          $this->numRows = count($this->result);
471
      }
472
      return $this->result;
473
    }
474
475
   /**
476
    * Setting the database configuration using the configuration file and additional configuration from param
477
    * @param array $overwriteConfig the additional configuration to overwrite with the existing one
478
    * @param boolean $useConfigFile whether to use database configuration file
479
    * @param boolean $autoConnect whether to connect to database after set the configuration
480
	  * @return object Database
481
    */
482
    public function setDatabaseConfiguration(array $overwriteConfig = array(), $useConfigFile = true, $autoConnect = false){
483
      $db = array();
484
      if ($useConfigFile && file_exists(CONFIG_PATH . 'database.php')){
485
          //here don't use require_once because somewhere user can create database instance directly
486
          require CONFIG_PATH . 'database.php';
487
      }
488
      
489
      //merge with the parameter  
490
      $db = array_merge($db, $overwriteConfig);
491
      
492
      //get the default configuration
493
      $config = $this->getDatabaseDefaultConfiguration();
494
		  
495
    	$config = array_merge($config, $db);
496
    	//determine the port using the hostname like localhost:3307
497
      //hostname will be "localhost", and port "3307"
498
      $p = explode(':', $config['hostname']);
499
  	  if (count($p) >= 2){
500
  		  $config['hostname'] = $p[0];
501
  		  $config['port'] = $p[1];
502
  		}
503
		
504
		 $this->databaseName = $config['database'];
505
		 $this->config = $config;
506
		 $this->logger->info(
507
								'The database configuration are listed below: ' 
508
								. stringfy_vars(array_merge(
509
															$this->config, 
510
															array('password' => string_hidden($this->config['password']))
511
												))
512
							);
513
  	  if($autoConnect){
514
    		 //Now connect to the database
515
    		 $this->connect();
516
  		}
517
		 return $this;
518
    }
519
520
    /**
521
   * Return the database configuration
522
   * @return array
523
   */
524
    public  function getDatabaseConfiguration(){
525
      return $this->config;
526
    }
527
528
    /**
529
     * Close the connexion
530
     */
531
    public function close(){
532
      $this->pdo = null;
533
    }
534
535
    /**
536
     * Return the database default configuration
537
     * @return array
538
     */
539
    protected function getDatabaseDefaultConfiguration(){
540
      return array(
541
              'driver' => '',
542
              'username' => '',
543
              'password' => '',
544
              'database' => '',
545
              'hostname' => 'localhost',
546
              'charset' => 'utf8',
547
              'collation' => 'utf8_general_ci',
548
              'prefix' => '',
549
              'port' => ''
550
            );
551
    }
552
553
    /**
554
     * Update the DatabaseQueryBuilder and DatabaseQueryRunner properties
555
     * @return void
556
     */
557
    protected function updateQueryBuilderAndRunnerProperties(){
558
       //update queryBuilder with some properties needed
559
     if (is_object($this->queryBuilder)){
560
        $this->queryBuilder->setDriver($this->config['driver'])
561
                           ->setPrefix($this->config['prefix'])
562
                           ->setPdo($this->pdo);
563
     }
564
565
      //update queryRunner with some properties needed
566
     if (is_object($this->queryRunner)){
567
        $this->queryRunner->setDriver($this->config['driver'])
568
                          ->setPdo($this->pdo);
569
     }
570
    }
571
	
572
573
    /**
574
     * This method is used to get the PDO DSN string using the configured driver
575
     * @return string|null the DSN string or null if can not find it
576
     */
577
    protected function getDsnValueFromConfig(){
578
      $dsn = null;
579
      $config = $this->getDatabaseConfiguration();
580
      if (! empty($config)){
581
        $driver = $config['driver'];
582
        $driverDsnMap = array(
583
                              'mysql'  => $this->getDsnValueForDriver('mysql'),
584
                              'pgsql'  => $this->getDsnValueForDriver('pgsql'),
585
                              'sqlite' => $this->getDsnValueForDriver('sqlite'),
586
                              'oracle' => $this->getDsnValueForDriver('oracle')
587
                              );
588
        if (isset($driverDsnMap[$driver])){
589
          $dsn = $driverDsnMap[$driver];
590
        }
591
      }    
592
      return $dsn;
593
    }
594
595
    /**
596
     * Get the DSN value for the given driver
597
     * @param  string $driver the driver name
598
     * @return string|null         the dsn name
599
     */
600
    protected function getDsnValueForDriver($driver){
601
      $dsn = '';
602
      $config = $this->getDatabaseConfiguration();
603
      if (empty($config)){
604
        return null;
605
      }
606
      switch ($driver) {
607
        case 'mysql':
608
        case 'pgsql':
609
          $port = '';
610
          if (! empty($config['port'])) {
611
            $port = 'port=' . $config['port'] . ';';
612
          }
613
          $dsn = $driver . ':host=' . $config['hostname'] . ';' . $port . 'dbname=' . $config['database'];
614
          break;
615
        case 'sqlite':
616
          $dsn = 'sqlite:' . $config['database'];
617
          break;
618
          case 'oracle':
619
          $port = '';
620
          if (! empty($config['port'])) {
621
            $port = ':' . $config['port'];
622
          }
623
          $dsn =  'oci:dbname=' . $config['hostname'] . $port . '/' . $config['database'];
624
          break;
625
      }
626
      return $dsn;
627
    }
628
629
    /**
630
     * Get the cache content for this query
631
     * @see Database::query
632
     *      
633
     * @return mixed
634
     */
635
    protected function getCacheContentForQuery($query, $returnAsList, $returnAsArray){
636
        $cacheKey = $this->getCacheKeyForQuery($query, $returnAsList, $returnAsArray);
637
        if (! is_object($this->cacheInstance)){
638
    			//can not call method with reference in argument
639
    			//like $this->setCacheInstance(& get_instance()->cache);
640
    			//use temporary variable
641
    			$instance = & get_instance()->cache;
642
    			$this->cacheInstance = $instance;
643
        }
644
        return $this->cacheInstance->get($cacheKey);
645
    }
646
647
    /**
648
     * Save the result of query into cache
649
     * @param string $query  the SQL query
650
     * @param string $key    the cache key
651
     * @param mixed $result the query result to save
652
     * @param int $expire the cache TTL
653
     */
654
     protected function setCacheContentForQuery($query, $key, $result, $expire){
655
        $this->logger->info('Save the result for query [' .$query. '] into cache for future use');
656
        if (! is_object($this->cacheInstance)){
657
  				//can not call method with reference in argument
658
  				//like $this->setCacheInstance(& get_instance()->cache);
659
  				//use temporary variable
660
  				$instance = & get_instance()->cache;
661
  				$this->cacheInstance = $instance;
662
  			}
663
        $this->cacheInstance->set($key, $result, $expire);
664
     }
665
666
    
667
	 /**
668
     * Return the cache key for the given query
669
     * @see Database::query
670
     * 
671
     *  @return string
672
     */
673
    protected function getCacheKeyForQuery($query, $returnAsList, $returnAsArray){
674
      return md5($query . $returnAsList . $returnAsArray);
675
    }
676
677
	
678
    /**
679
     * Reset the database class attributs to the initail values before each query.
680
     */
681
    private function reset(){
682
	   //query builder reset
683
      $this->queryBuilder->reset();
684
      $this->numRows  = 0;
685
      $this->insertId = null;
686
      $this->query    = null;
687
      $this->result   = array();
688
      $this->data     = array();
689
    }
690
691
    /**
692
     * The class destructor
693
     */
694
    public function __destruct(){
695
      $this->pdo = null;
696
    }
697
698
}
699