Completed
Branch master (099915)
by Fabio
08:02
created

TDbCache::getUsername()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
1
<?php
2
/**
3
 * TDbCache class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 * @package Prado\Caching
9
 */
10
11
namespace Prado\Caching;
12
13
use Prado\Prado;
14
use Prado\Data\TDataSourceConfig;
15
use Prado\Data\TDbConnection;
16
use Prado\Exceptions\TConfigurationException;
17
use Prado\TPropertyValue;
18
19
/**
20
 * TDbCache class
21
 *
22
 * TDbCache implements a cache application module by storing cached data in a database.
23
 *
24
 * TDbCache relies on {@link http://www.php.net/manual/en/ref.pdo.php PDO} to retrieve
25
 * data from databases. In order to use TDbCache, you need to enable the PDO extension
26
 * as well as the corresponding PDO DB driver. For example, to use SQLite database
27
 * to store cached data, you need both php_pdo and php_pdo_sqlite extensions.
28
 *
29
 * By default, TDbCache creates and uses an SQLite database under the application
30
 * runtime directory. You may change this default setting by specifying the following
31
 * properties:
32
 * - {@link setConnectionID ConnectionID} or
33
 * - {@link setConnectionString ConnectionString}, {@link setUsername Username} and {@link setPassword Pasword}.
34
 *
35
 * The cached data is stored in a table in the specified database.
36
 * By default, the name of the table is called 'pradocache'. If the table does not
37
 * exist in the database, it will be automatically created with the following structure:
38
 * <code>
39
 * CREATE TABLE pradocache (itemkey CHAR(128), value BLOB, expire INT)
40
 * CREATE INDEX IX_itemkey ON pradocache (itemkey)
41
 * CREATE INDEX IX_expire ON pradocache (expire)
42
 * </code>
43
 *
44
 * Note, some DBMS might not support BLOB type. In this case, replace 'BLOB' with a suitable
45
 * binary data type (e.g. LONGBLOB in MySQL, BYTEA in PostgreSQL.)
46
 *
47
 * Important: Make sure that the indices are non-unique!
48
 *
49
 * If you want to change the cache table name, or if you want to create the table by yourself,
50
 * you may set {@link setCacheTableName CacheTableName} and {@link setAutoCreateCacheTable AutoCreateCacheTableName} properties.
51
 *
52
 * {@link setFlushInterval FlushInterval} control how often expired items will be removed from cache.
53
 * If you prefer to remove expired items manualy e.g. via cronjob you can disable automatic deletion by setting FlushInterval to '0'.
54
 *
55
 * The following basic cache operations are implemented:
56
 * - {@link get} : retrieve the value with a key (if any) from cache
57
 * - {@link set} : store the value with a key into cache
58
 * - {@link add} : store the value only if cache does not have this key
59
 * - {@link delete} : delete the value with the specified key from cache
60
 * - {@link flush} : delete all values from cache
61
 *
62
 * Each value is associated with an expiration time. The {@link get} operation
63
 * ensures that any expired value will not be returned. The expiration time by
64
 * the number of seconds. A expiration time 0 represents never expire.
65
 *
66
 * By definition, cache does not ensure the existence of a value
67
 * even if it never expires. Cache is not meant to be an persistent storage.
68
 *
69
 * Do not use the same database file for multiple applications using TDbCache.
70
 * Also note, cache is shared by all user sessions of an application.
71
 *
72
 * Some usage examples of TDbCache are as follows,
73
 * <code>
74
 * $cache=new TDbCache;  // TDbCache may also be loaded as a Prado application module
75
 * $cache->init(null);
76
 * $cache->add('object',$object);
77
 * $object2=$cache->get('object');
78
 * </code>
79
 *
80
 * If loaded, TDbCache will register itself with {@link TApplication} as the
81
 * cache module. It can be accessed via {@link TApplication::getCache()}.
82
 *
83
 * TDbCache may be configured in application configuration file as follows
84
 * <code>
85
 * <module id="cache" class="Prado\Caching\TDbCache" />
86
 * </code>
87
 *
88
 * @author Qiang Xue <[email protected]>
89
 * @package Prado\Caching
90
 * @since 3.1.0
91
 */
92
class TDbCache extends TCache
93
{
94
	/**
95
	 * @var string the ID of TDataSourceConfig module
96
	 */
97
	private $_connID = '';
98
	/**
99
	 * @var TDbConnection the DB connection instance
100
	 */
101
	private $_db;
102
	/**
103
	 * @var string name of the DB cache table
104
	 */
105
	private $_cacheTable = 'pradocache';
106
	/**
107
	 * @var int Interval expired items will be removed from cache
108
	 */
109
	private $_flushInterval = 60;
110
	/**
111
	 * @var bool
112
	 */
113
	private $_cacheInitialized = false;
114
	/**
115
	 * @var bool
116
	 */
117
	private $_createCheck = false;
118
	/**
119
	 * @var bool whether the cache DB table should be created automatically
120
	 */
121
	private $_autoCreate = true;
122
	private $_username = '';
123
	private $_password = '';
124
	private $_connectionString = '';
125
126
	/**
127
	 * Destructor.
128
	 * Disconnect the db connection.
129
	 */
130
	public function __destruct()
131
	{
132
		if ($this->_db !== null) {
133
			$this->_db->setActive(false);
134
		}
135
	}
136
137
	/**
138
	 * Initializes this module.
139
	 * This method is required by the IModule interface.
140
	 * attach {@link doInitializeCache} to TApplication.OnLoadStateComplete event
141
	 * attach {@link doFlushCacheExpired} to TApplication.OnSaveState event
142
	 *
143
	 * @param TXmlElement $config configuration for this module, can be null
0 ignored issues
show
Bug introduced by
The type Prado\Caching\TXmlElement was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
144
	 */
145
	public function init($config)
146
	{
147
		$this -> getApplication() -> attachEventHandler('OnLoadStateComplete', [$this, 'doInitializeCache']);
148
		$this -> getApplication() -> attachEventHandler('OnSaveState', [$this, 'doFlushCacheExpired']);
149
		parent::init($config);
150
	}
151
152
	/**
153
	 * Event listener for TApplication.OnSaveState
154
	 * @return void
155
	 * @since 3.1.5
156
	 * @see flushCacheExpired
157
	 */
158
	public function doFlushCacheExpired()
159
	{
160
		$this->flushCacheExpired(false);
161
	}
162
163
	/**
164
	 * Event listener for TApplication.OnLoadStateComplete
165
	 *
166
	 * @return void
167
	 * @since 3.1.5
168
	 * @see initializeCache
169
	 */
170
	public function doInitializeCache()
171
	{
172
		$this->initializeCache();
173
	}
174
175
	/**
176
	 * Initialize TDbCache
177
	 *
178
	 * If {@link setAutoCreateCacheTable AutoCreateCacheTableName} is 'true' check existence of cache table
179
	 * and create table if does not exist.
180
	 *
181
	 * @param bool $force Force override global state check
182
	 * @throws TConfigurationException if any error happens during creating database or cache table.
183
	 * @return void
184
	 * @since 3.1.5
185
	 */
186
	protected function initializeCache($force = false)
187
	{
188
		if ($this->_cacheInitialized && !$force) {
189
			return;
190
		}
191
		$db = $this->getDbConnection();
192
		try {
193
			$key = 'TDbCache:' . $this->_cacheTable . ':created';
194
			if ($force) {
195
				$this -> _createCheck = false;
196
			} else {
197
				$this -> _createCheck = $this -> getApplication() -> getGlobalState($key, 0);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getApplication()->getGlobalState($key, 0) can also be of type integer. However, the property $_createCheck is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
198
			}
199
200
			if ($this->_autoCreate && !$this -> _createCheck) {
201
				Prado::trace(($force ? 'Force initializing: ' : 'Initializing: ') . $this -> id . ', ' . $this->_cacheTable, '\Prado\Caching\TDbCache');
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on Prado\Caching\TDbCache. Since you implemented __get, consider adding a @property annotation.
Loading history...
202
203
				$sql = 'SELECT 1 FROM ' . $this->_cacheTable . ' WHERE 0=1';
204
				$db->createCommand($sql)->queryScalar();
205
206
				$this -> _createCheck = true;
207
				$this -> getApplication() -> setGlobalState($key, time());
208
			}
209
		} catch (\Exception $e) {
210
			// DB table not exists
211
			if ($this->_autoCreate) {
212
				Prado::trace('Autocreate: ' . $this->_cacheTable, '\Prado\Caching\TDbCache');
213
214
				$driver = $db->getDriverName();
215
				if ($driver === 'mysql') {
216
					$blob = 'LONGBLOB';
217
				} elseif ($driver === 'pgsql') {
218
					$blob = 'BYTEA';
219
				} else {
220
					$blob = 'BLOB';
221
				}
222
223
				$sql = 'CREATE TABLE ' . $this->_cacheTable . " (itemkey CHAR(128) PRIMARY KEY, value $blob, expire INTEGER)";
224
				$db->createCommand($sql)->execute();
225
226
				$sql = 'CREATE INDEX IX_expire ON ' . $this->_cacheTable . ' (expire)';
227
				$db->createCommand($sql)->execute();
228
229
				$this -> _createCheck = true;
230
				$this -> getApplication() -> setGlobalState($key, time());
231
			} else {
232
				throw new TConfigurationException('db_cachetable_inexistent', $this->_cacheTable);
233
			}
234
		}
235
		$this->_cacheInitialized = true;
236
	}
237
238
	/**
239
	 * Flush expired values from cache depending on {@link setFlushInterval FlushInterval}
240
	 * @param bool $force override {@link setFlushInterval FlushInterval} and force deletion of expired items
241
	 * @return void
242
	 * @since 3.1.5
243
	 */
244
	public function flushCacheExpired($force = false)
245
	{
246
		$interval = $this -> getFlushInterval();
247
		if (!$force && $interval === 0) {
248
			return;
249
		}
250
251
		$key = 'TDbCache:' . $this->_cacheTable . ':flushed';
252
		$now = time();
253
		$next = $interval + (integer) $this -> getApplication() -> getGlobalState($key, 0);
254
255
		if ($force || $next <= $now) {
256
			if (!$this->_cacheInitialized) {
257
				$this->initializeCache();
258
			}
259
			Prado::trace(($force ? 'Force flush of expired items: ' : 'Flush expired items: ') . $this -> id . ', ' . $this->_cacheTable, '\Prado\Caching\TDbCache');
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on Prado\Caching\TDbCache. Since you implemented __get, consider adding a @property annotation.
Loading history...
260
			$sql = 'DELETE FROM ' . $this->_cacheTable . ' WHERE expire<>0 AND expire<' . $now;
261
			$this->getDbConnection()->createCommand($sql)->execute();
262
			$this -> getApplication() -> setGlobalState($key, $now);
263
		}
264
	}
265
266
	/**
267
	 * @return int Interval in sec expired items will be removed from cache. Default to 60
268
	 * @since 3.1.5
269
	 */
270
	public function getFlushInterval()
271
	{
272
		return $this->_flushInterval;
273
	}
274
275
	/**
276
	 * Sets interval expired items will be removed from cache
277
	 *
278
	 * To disable automatic deletion of expired items,
279
	 * e.g. for external flushing via cron you can set value to '0'
280
	 *
281
	 * @param int $value Interval in sec
282
	 * @since 3.1.5
283
	 */
284
	public function setFlushInterval($value)
285
	{
286
		$this->_flushInterval = (integer) $value;
287
	}
288
289
	/**
290
	 * Creates the DB connection.
291
	 * @throws TConfigurationException if module ID is invalid or empty
292
	 * @return TDbConnection the created DB connection
293
	 */
294
	protected function createDbConnection()
295
	{
296
		if ($this->_connID !== '') {
297
			$config = $this->getApplication()->getModule($this->_connID);
298
			if ($config instanceof TDataSourceConfig) {
299
				return $config->getDbConnection();
300
			} else {
301
				throw new TConfigurationException('dbcache_connectionid_invalid', $this->_connID);
302
			}
303
		} else {
304
			$db = new TDbConnection;
305
			if ($this->_connectionString !== '') {
306
				$db->setConnectionString($this->_connectionString);
307
				if ($this->_username !== '') {
308
					$db->setUsername($this->_username);
309
				}
310
				if ($this->_password !== '') {
311
					$db->setPassword($this->_password);
312
				}
313
			} else {
314
				// default to SQLite3 database
315
				$dbFile = $this->getApplication()->getRuntimePath() . '/sqlite3.cache';
316
				$db->setConnectionString('sqlite:' . $dbFile);
317
			}
318
			return $db;
319
		}
320
	}
321
322
	/**
323
	 * @return TDbConnection the DB connection instance
324
	 */
325
	public function getDbConnection()
326
	{
327
		if ($this->_db === null) {
328
			$this->_db = $this->createDbConnection();
329
		}
330
331
		$this->_db->setActive(true);
332
		return $this->_db;
333
	}
334
335
	/**
336
	 * @return string the ID of a {@link TDataSourceConfig} module. Defaults to empty string, meaning not set.
337
	 * @since 3.1.1
338
	 */
339
	public function getConnectionID()
340
	{
341
		return $this->_connID;
342
	}
343
344
	/**
345
	 * Sets the ID of a TDataSourceConfig module.
346
	 * The datasource module will be used to establish the DB connection for this cache module.
347
	 * The database connection can also be specified via {@link setConnectionString ConnectionString}.
348
	 * When both ConnectionID and ConnectionString are specified, the former takes precedence.
349
	 * @param string $value ID of the {@link TDataSourceConfig} module
350
	 * @since 3.1.1
351
	 */
352
	public function setConnectionID($value)
353
	{
354
		$this->_connID = $value;
355
	}
356
357
	/**
358
	 * @return string The Data Source Name, or DSN, contains the information required to connect to the database.
359
	 */
360
	public function getConnectionString()
361
	{
362
		return $this->_connectionString;
363
	}
364
365
	/**
366
	 * @param string $value The Data Source Name, or DSN, contains the information required to connect to the database.
367
	 * @see http://www.php.net/manual/en/function.pdo-construct.php
368
	 */
369
	public function setConnectionString($value)
370
	{
371
		$this->_connectionString = $value;
372
	}
373
374
	/**
375
	 * @return string the username for establishing DB connection. Defaults to empty string.
376
	 */
377
	public function getUsername()
378
	{
379
		return $this->_username;
380
	}
381
382
	/**
383
	 * @param string $value the username for establishing DB connection
384
	 */
385
	public function setUsername($value)
386
	{
387
		$this->_username = $value;
388
	}
389
390
	/**
391
	 * @return string the password for establishing DB connection. Defaults to empty string.
392
	 */
393
	public function getPassword()
394
	{
395
		return $this->_password;
396
	}
397
398
	/**
399
	 * @param string $value the password for establishing DB connection
400
	 */
401
	public function setPassword($value)
402
	{
403
		$this->_password = $value;
404
	}
405
406
	/**
407
	 * @return string the name of the DB table to store cache content. Defaults to 'pradocache'.
408
	 * @see setAutoCreateCacheTable
409
	 */
410
	public function getCacheTableName()
411
	{
412
		return $this->_cacheTable;
413
	}
414
415
	/**
416
	 * Sets the name of the DB table to store cache content.
417
	 * Note, if {@link setAutoCreateCacheTable AutoCreateCacheTable} is false
418
	 * and you want to create the DB table manually by yourself,
419
	 * you need to make sure the DB table is of the following structure:
420
	 * <code>
421
	 * CREATE TABLE pradocache (itemkey CHAR(128), value BLOB, expire INT)
422
	 * CREATE INDEX IX_itemkey ON pradocache (itemkey)
423
	 * CREATE INDEX IX_expire ON pradocache (expire)
424
	 * </code>
425
	 *
426
	 * Note, some DBMS might not support BLOB type. In this case, replace 'BLOB' with a suitable
427
	 * binary data type (e.g. LONGBLOB in MySQL, BYTEA in PostgreSQL.)
428
	 *
429
	 * Important: Make sure that the indices are non-unique!
430
	 *
431
	 * @param string $value the name of the DB table to store cache content
432
	 * @see setAutoCreateCacheTable
433
	 */
434
	public function setCacheTableName($value)
435
	{
436
		$this->_cacheTable = $value;
437
	}
438
439
	/**
440
	 * @return bool whether the cache DB table should be automatically created if not exists. Defaults to true.
441
	 * @see setAutoCreateCacheTable
442
	 */
443
	public function getAutoCreateCacheTable()
444
	{
445
		return $this->_autoCreate;
446
	}
447
448
	/**
449
	 * @param bool $value whether the cache DB table should be automatically created if not exists.
450
	 * @see setCacheTableName
451
	 */
452
	public function setAutoCreateCacheTable($value)
453
	{
454
		$this->_autoCreate = TPropertyValue::ensureBoolean($value);
455
	}
456
457
	/**
458
	 * Retrieves a value from cache with a specified key.
459
	 * This is the implementation of the method declared in the parent class.
460
	 * @param string $key a unique key identifying the cached value
461
	 * @return string the value stored in cache, false if the value is not in the cache or expired.
462
	 */
463
	protected function getValue($key)
464
	{
465
		if (!$this->_cacheInitialized) {
466
			$this->initializeCache();
467
		}
468
		try {
469
			$sql = 'SELECT value FROM ' . $this->_cacheTable . ' WHERE itemkey=\'' . $key . '\' AND (expire=0 OR expire>' . time() . ') ORDER BY expire DESC';
470
			$command = $this->getDbConnection()->createCommand($sql);
471
			return unserialize($command->queryScalar());
472
		} catch (\Exception $e) {
473
			$this->initializeCache(true);
474
			return unserialize($command->queryScalar());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $command does not seem to be defined for all execution paths leading up to this point.
Loading history...
475
		}
476
	}
477
478
	/**
479
	 * Stores a value identified by a key in cache.
480
	 * This is the implementation of the method declared in the parent class.
481
	 *
482
	 * @param string $key the key identifying the value to be cached
483
	 * @param string $value the value to be cached
484
	 * @param int $expire the number of seconds in which the cached value will expire. 0 means never expire.
485
	 * @return bool true if the value is successfully stored into cache, false otherwise
486
	 */
487
	protected function setValue($key, $value, $expire)
488
	{
489
		$this->deleteValue($key);
490
		return $this->addValue($key, $value, $expire);
491
	}
492
493
	/**
494
	 * Stores a value identified by a key into cache if the cache does not contain this key.
495
	 * This is the implementation of the method declared in the parent class.
496
	 *
497
	 * @param string $key the key identifying the value to be cached
498
	 * @param string $value the value to be cached
499
	 * @param int $expire the number of seconds in which the cached value will expire. 0 means never expire.
500
	 * @return bool true if the value is successfully stored into cache, false otherwise
501
	 */
502
	protected function addValue($key, $value, $expire)
503
	{
504
		if (!$this->_cacheInitialized) {
505
			$this->initializeCache();
506
		}
507
		$expire = ($expire <= 0) ? 0 : time() + $expire;
508
		$sql = "INSERT INTO {$this->_cacheTable} (itemkey,value,expire) VALUES(:key,:value,$expire)";
509
		try {
510
			$command = $this->getDbConnection()->createCommand($sql);
511
			$command->bindValue(':key', $key, \PDO::PARAM_STR);
512
			$command->bindValue(':value', serialize($value), \PDO::PARAM_LOB);
513
			$command->execute();
514
			return true;
515
		} catch (\Exception $e) {
516
			try {
517
				$this->initializeCache(true);
518
				$command->execute();
519
				return true;
520
			} catch (\Exception $e) {
521
				return false;
522
			}
523
		}
524
	}
525
526
	/**
527
	 * Deletes a value with the specified key from cache
528
	 * This is the implementation of the method declared in the parent class.
529
	 * @param string $key the key of the value to be deleted
530
	 * @return bool if no error happens during deletion
531
	 */
532
	protected function deleteValue($key)
533
	{
534
		if (!$this->_cacheInitialized) {
535
			$this->initializeCache();
536
		}
537
		try {
538
			$command = $this->getDbConnection()->createCommand("DELETE FROM {$this->_cacheTable} WHERE itemkey=:key");
539
			$command->bindValue(':key', $key, \PDO::PARAM_STR);
540
			$command->execute();
541
			return true;
542
		} catch (\Exception $e) {
543
			$this->initializeCache(true);
544
			$command->execute();
545
			return true;
546
		}
547
	}
548
549
	/**
550
	 * Deletes all values from cache.
551
	 * Be careful of performing this operation if the cache is shared by multiple applications.
552
	 */
553
	public function flush()
554
	{
555
		if (!$this->_cacheInitialized) {
556
			$this->initializeCache();
557
		}
558
		try {
559
			$command = $this->getDbConnection()->createCommand("DELETE FROM {$this->_cacheTable}");
560
			$command->execute();
561
		} catch (\Exception $e) {
562
			try {
563
				$this->initializeCache(true);
564
				$command->execute();
565
				return true;
566
			} catch (\Exception $e) {
567
				return false;
568
			}
569
		}
570
		return true;
571
	}
572
}
573