Test Failed
Push — 1.0.0-dev ( dc6bfa...14bd99 )
by nguereza
02:55
created

Database::setLoggerFromParamOrCreate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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