Issues (1474)

framework/Caching/TDbCache.php (4 issues)

1
<?php
2
3
/**
4
 * TDbCache class file
5
 *
6
 * @author Qiang Xue <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
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
use Prado\Util\Cron\TCronTaskInfo;
19
20
/**
21
 * TDbCache class
22
 *
23
 * TDbCache implements a cache application module by storing cached data in a database.
24
 *
25
 * TDbCache relies on {@see http://www.php.net/manual/en/ref.pdo.php PDO} to retrieve
26
 * data from databases. In order to use TDbCache, you need to enable the PDO extension
27
 * as well as the corresponding PDO DB driver. For example, to use SQLite database
28
 * to store cached data, you need both php_pdo and php_pdo_sqlite extensions.
29
 *
30
 * By default, TDbCache creates and uses an SQLite database under the application
31
 * runtime directory. You may change this default setting by specifying the following
32
 * properties:
33
 * - {@see \Prado\Caching\TDbCache::setConnectionID() ConnectionID} or
34
 * - {@see \Prado\Caching\TDbCache::setConnectionString() ConnectionString}, {@see \Prado\Caching\TDbCache::setUsername() Username} and {@see \Prado\Caching\TDbCache::setPassword() Pasword}.
35
 *
36
 * The cached data is stored in a table in the specified database.
37
 * By default, the name of the table is called 'pradocache'. If the table does not
38
 * exist in the database, it will be automatically created with the following structure:
39
 * ```sql
40
 * CREATE TABLE pradocache (itemkey CHAR(128), value BLOB, expire INT)
41
 * CREATE INDEX IX_itemkey ON pradocache (itemkey)
42
 * CREATE INDEX IX_expire ON pradocache (expire)
43
 * ```
44
 *
45
 * Note, some DBMS might not support BLOB type. In this case, replace 'BLOB' with a suitable
46
 * binary data type (e.g. LONGBLOB in MySQL, BYTEA in PostgreSQL.)
47
 *
48
 * Important: Make sure that the indices are non-unique!
49
 *
50
 * If you want to change the cache table name, or if you want to create the table by yourself,
51
 * you may set {@see \Prado\Caching\TDbCache::setCacheTableName() CacheTableName} and {@see \Prado\Caching\TDbCache::setAutoCreateCacheTable() AutoCreateCacheTableName} properties.
52
 *
53
 * {@see \Prado\Caching\TDbCache::setFlushInterval() FlushInterval} control how often expired items will be removed from cache.
54
 * If you prefer to remove expired items manualy e.g. via cronjob you can disable automatic deletion by setting FlushInterval to '0'.
55
 *
56
 * The following basic cache operations are implemented:
57
 * - {@see self::get()} : retrieve the value with a key (if any) from cache
58
 * - {@see \Prado\Caching\TDbCache::set()} : store the value with a key into cache
59
 * - {@see \Prado\Caching\TDbCache::add()} : store the value only if cache does not have this key
60
 * - {@see \Prado\Caching\TDbCache::delete()} : delete the value with the specified key from cache
61
 * - {@see \Prado\Caching\TDbCache::flush()} : delete all values from cache
62
 *
63
 * Each value is associated with an expiration time. The {@see \Prado\Caching\TDbCache::get()} operation
64
 * ensures that any expired value will not be returned. The expiration time by
65
 * the number of seconds. A expiration time 0 represents never expire.
66
 *
67
 * By definition, cache does not ensure the existence of a value
68
 * even if it never expires. Cache is not meant to be an persistent storage.
69
 *
70
 * Do not use the same database file for multiple applications using TDbCache.
71
 * Also note, cache is shared by all user sessions of an application.
72
 *
73
 * Some usage examples of TDbCache are as follows,
74
 * ```php
75
 * $cache=new TDbCache;  // TDbCache may also be loaded as a Prado application module
76
 * $cache->init(null);
77
 * $cache->add('object',$object);
78
 * $object2=$cache->get('object');
79
 * ```
80
 *
81
 * If loaded, TDbCache will register itself with {@see \Prado\TApplication} as the
82
 * cache module. It can be accessed via {@see \Prado\TApplication::getCache()}.
83
 *
84
 * TDbCache may be configured in application configuration file as follows
85
 * ```xml
86
 * <module id="cache" class="Prado\Caching\TDbCache" />
87
 * ```
88
 *
89
 * @author Qiang Xue <[email protected]>
90
 * @since 3.1.0
91
 */
92
class TDbCache extends TCache implements \Prado\Util\IDbModule
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
		parent::__destruct();
136
	}
137
138
	/**
139
	 * Initializes this module.
140
	 * This method is required by the IModule interface.
141
	 * attach {@see \Prado\Caching\TDbCache::doInitializeCache()} to TApplication.OnLoadStateComplete event
142
	 * attach {@see \Prado\Caching\TDbCache::doFlushCacheExpired()} to TApplication.OnSaveState event
143
	 * @param \Prado\Xml\TXmlElement $config configuration for this module, can be null
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
	 * @since 3.1.5
155
	 * @see flushCacheExpired
156
	 */
157
	public function doFlushCacheExpired()
158
	{
159
		$this->flushCacheExpired(false);
160
	}
161
162
	/**
163
	 * Event listener for TApplication.OnLoadStateComplete
164
	 *
165
	 * @since 3.1.5
166
	 * @see initializeCache
167
	 */
168
	public function doInitializeCache()
169
	{
170
		$this->initializeCache();
171
	}
172
173
	/**
174
	 * Initialize TDbCache
175
	 *
176
	 * If {@see \Prado\Caching\TDbCache::setAutoCreateCacheTable() AutoCreateCacheTableName} is 'true' check existence of cache table
177
	 * and create table if does not exist.
178
	 *
179
	 * @param bool $force Force override global state check
180
	 * @throws TConfigurationException if any error happens during creating database or cache table.
181
	 * @since 3.1.5
182
	 */
183
	protected function initializeCache($force = false)
184
	{
185
		if ($this->_cacheInitialized && !$force) {
186
			return;
187
		}
188
		$db = $this->getDbConnection();
189
		try {
190
			$key = 'TDbCache:' . $this->_cacheTable . ':created';
191
			if ($force) {
192
				$this->_createCheck = false;
193
			} else {
194
				$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...
195
			}
196
197
			if ($this->_autoCreate && !$this->_createCheck) {
198
				Prado::trace(($force ? 'Force initializing: ' : 'Initializing: ') . $this->_connID . ', ' . $this->_cacheTable, TDbCache::class);
199
200
				$sql = 'SELECT 1 FROM ' . $this->_cacheTable . ' WHERE 0=1';
201
				$db->createCommand($sql)->queryScalar();
202
203
				$this->_createCheck = true;
204
				$this->getApplication()->setGlobalState($key, time());
205
			}
206
		} catch (\Exception $e) {
207
			// DB table not exists
208
			if ($this->_autoCreate) {
209
				Prado::trace('Autocreate: ' . $this->_cacheTable, TDbCache::class);
210
211
				$driver = $db->getDriverName();
212
				if ($driver === 'mysql') {
213
					$blob = 'LONGBLOB';
214
				} elseif ($driver === 'pgsql') {
215
					$blob = 'BYTEA';
216
				} else {
217
					$blob = 'BLOB';
218
				}
219
220
				$sql = 'CREATE TABLE ' . $this->_cacheTable . " (itemkey CHAR(128) PRIMARY KEY, value $blob, expire INTEGER)";
221
				$db->createCommand($sql)->execute();
222
223
				$sql = 'CREATE INDEX IX_expire ON ' . $this->_cacheTable . ' (expire)';
224
				$db->createCommand($sql)->execute();
225
226
				$this->_createCheck = true;
227
				$this->getApplication()->setGlobalState($key, time());
228
			} else {
229
				throw new TConfigurationException('db_cachetable_inexistent', $this->_cacheTable);
230
			}
231
		}
232
		$this->_cacheInitialized = true;
233
	}
234
235
	/**
236
	 * Flush expired values from cache depending on {@see \Prado\Caching\TDbCache::setFlushInterval() FlushInterval}
237
	 * @param bool $force override {@see \Prado\Caching\TDbCache::setFlushInterval() FlushInterval} and force deletion of expired items
238
	 * @since 3.1.5
239
	 */
240
	public function flushCacheExpired($force = false)
241
	{
242
		$interval = $this->getFlushInterval();
243
		if (!$force && $interval === 0) {
244
			return;
245
		}
246
247
		$key = 'TDbCache:' . $this->_cacheTable . ':flushed';
248
		$now = time();
249
		$next = $interval + (int) $this->getApplication()->getGlobalState($key, 0);
250
251
		if ($force || $next <= $now) {
252
			if (!$this->_cacheInitialized) {
253
				$this->initializeCache();
254
			}
255
			Prado::trace(($force ? 'Force flush of expired items: ' : 'Flush expired items: ') . $this->_connID . ', ' . $this->_cacheTable, TDbCache::class);
256
			$sql = 'DELETE FROM ' . $this->_cacheTable . ' WHERE expire<>0 AND expire<' . $now;
257
			$this->getDbConnection()->createCommand($sql)->execute();
258
			$this->getApplication()->setGlobalState($key, $now);
259
		}
260
	}
261
262
	/**
263
	 * @param object $sender the object raising fxGetCronTaskInfos.
264
	 * @param mixed $param the parameter
265
	 * @since 4.2.0
266
	 */
267
	public function fxGetCronTaskInfos($sender, $param)
0 ignored issues
show
The parameter $sender is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

267
	public function fxGetCronTaskInfos(/** @scrutinizer ignore-unused */ $sender, $param)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $param is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

267
	public function fxGetCronTaskInfos($sender, /** @scrutinizer ignore-unused */ $param)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
268
	{
269
		return new TCronTaskInfo('dbcacheflushexpired', $this->getId() . '->flushCacheExpired(true)', $this, Prado::localize('DbCache Flush Expired Keys'), Prado::localize('This manually clears out the expired keys of TDbCache.'));
270
	}
271
272
	/**
273
	 * @return int Interval in sec expired items will be removed from cache. Default to 60
274
	 * @since 3.1.5
275
	 */
276
	public function getFlushInterval()
277
	{
278
		return $this->_flushInterval;
279
	}
280
281
	/**
282
	 * Sets interval expired items will be removed from cache
283
	 *
284
	 * To disable automatic deletion of expired items,
285
	 * e.g. for external flushing via cron you can set value to '0'
286
	 *
287
	 * @param int $value Interval in sec
288
	 * @since 3.1.5
289
	 */
290
	public function setFlushInterval($value)
291
	{
292
		$this->_flushInterval = (int) $value;
293
	}
294
295
	/**
296
	 * Creates the DB connection.
297
	 * @throws TConfigurationException if module ID is invalid or empty
298
	 * @return \Prado\Data\TDbConnection the created DB connection
299
	 */
300
	protected function createDbConnection()
301
	{
302
		if ($this->_connID !== '') {
303
			$config = $this->getApplication()->getModule($this->_connID);
304
			if ($config instanceof TDataSourceConfig) {
305
				return $config->getDbConnection();
306
			} else {
307
				throw new TConfigurationException('dbcache_connectionid_invalid', $this->_connID);
308
			}
309
		} else {
310
			$db = new TDbConnection();
311
			if ($this->_connectionString !== '') {
312
				$db->setConnectionString($this->_connectionString);
313
				if ($this->_username !== '') {
314
					$db->setUsername($this->_username);
315
				}
316
				if ($this->_password !== '') {
317
					$db->setPassword($this->_password);
318
				}
319
			} else {
320
				// default to SQLite3 database
321
				$dbFile = $this->getApplication()->getRuntimePath() . '/sqlite3.cache';
322
				$db->setConnectionString('sqlite:' . $dbFile);
323
			}
324
			return $db;
325
		}
326
	}
327
328
	/**
329
	 * @return \Prado\Data\TDbConnection the DB connection instance
330
	 */
331
	public function getDbConnection()
332
	{
333
		if ($this->_db === null) {
334
			$this->_db = $this->createDbConnection();
335
		}
336
337
		$this->_db->setActive(true);
338
		return $this->_db;
339
	}
340
341
	/**
342
	 * @return string the ID of a {@see \Prado\Data\TDataSourceConfig} module. Defaults to empty string, meaning not set.
343
	 * @since 3.1.1
344
	 */
345
	public function getConnectionID()
346
	{
347
		return $this->_connID;
348
	}
349
350
	/**
351
	 * Sets the ID of a TDataSourceConfig module.
352
	 * The datasource module will be used to establish the DB connection for this cache module.
353
	 * The database connection can also be specified via {@see \Prado\Caching\TDbCache::setConnectionString() ConnectionString}.
354
	 * When both ConnectionID and ConnectionString are specified, the former takes precedence.
355
	 * @param string $value ID of the {@see \Prado\Data\TDataSourceConfig} module
356
	 * @since 3.1.1
357
	 */
358
	public function setConnectionID($value)
359
	{
360
		$this->_connID = $value;
361
	}
362
363
	/**
364
	 * @return string The Data Source Name, or DSN, contains the information required to connect to the database.
365
	 */
366
	public function getConnectionString()
367
	{
368
		return $this->_connectionString;
369
	}
370
371
	/**
372
	 * @param string $value The Data Source Name, or DSN, contains the information required to connect to the database.
373
	 * @see http://www.php.net/manual/en/function.pdo-construct.php
374
	 */
375
	public function setConnectionString($value)
376
	{
377
		$this->_connectionString = $value;
378
	}
379
380
	/**
381
	 * @return string the username for establishing DB connection. Defaults to empty string.
382
	 */
383
	public function getUsername()
384
	{
385
		return $this->_username;
386
	}
387
388
	/**
389
	 * @param string $value the username for establishing DB connection
390
	 */
391
	public function setUsername($value)
392
	{
393
		$this->_username = $value;
394
	}
395
396
	/**
397
	 * @return string the password for establishing DB connection. Defaults to empty string.
398
	 */
399
	public function getPassword()
400
	{
401
		return $this->_password;
402
	}
403
404
	/**
405
	 * @param string $value the password for establishing DB connection
406
	 */
407
	public function setPassword(#[\SensitiveParameter] $value)
408
	{
409
		$this->_password = $value;
410
	}
411
412
	/**
413
	 * @return string the name of the DB table to store cache content. Defaults to 'pradocache'.
414
	 * @see setAutoCreateCacheTable
415
	 */
416
	public function getCacheTableName()
417
	{
418
		return $this->_cacheTable;
419
	}
420
421
	/**
422
	 * Sets the name of the DB table to store cache content.
423
	 * Note, if {@see \Prado\Caching\TDbCache::setAutoCreateCacheTable() AutoCreateCacheTable} is false
424
	 * and you want to create the DB table manually by yourself,
425
	 * you need to make sure the DB table is of the following structure:
426
	 * ```sql
427
	 * CREATE TABLE pradocache (itemkey CHAR(128), value BLOB, expire INT)
428
	 * CREATE INDEX IX_itemkey ON pradocache (itemkey)
429
	 * CREATE INDEX IX_expire ON pradocache (expire)
430
	 * ```
431
	 *
432
	 * Note, some DBMS might not support BLOB type. In this case, replace 'BLOB' with a suitable
433
	 * binary data type (e.g. LONGBLOB in MySQL, BYTEA in PostgreSQL.)
434
	 *
435
	 * Important: Make sure that the indices are non-unique!
436
	 *
437
	 * @param string $value the name of the DB table to store cache content
438
	 * @see setAutoCreateCacheTable
439
	 */
440
	public function setCacheTableName($value)
441
	{
442
		$this->_cacheTable = $value;
443
	}
444
445
	/**
446
	 * @return bool whether the cache DB table should be automatically created if not exists. Defaults to true.
447
	 * @see setAutoCreateCacheTable
448
	 */
449
	public function getAutoCreateCacheTable()
450
	{
451
		return $this->_autoCreate;
452
	}
453
454
	/**
455
	 * @param bool $value whether the cache DB table should be automatically created if not exists.
456
	 * @see setCacheTableName
457
	 */
458
	public function setAutoCreateCacheTable($value)
459
	{
460
		$this->_autoCreate = TPropertyValue::ensureBoolean($value);
461
	}
462
463
	/**
464
	 * Retrieves a value from cache with a specified key.
465
	 * This is the implementation of the method declared in the parent class.
466
	 * @param string $key a unique key identifying the cached value
467
	 * @return false|string the value stored in cache, false if the value is not in the cache or expired.
468
	 */
469
	protected function getValue($key)
470
	{
471
		if (!$this->_cacheInitialized) {
472
			$this->initializeCache();
473
		}
474
475
		$sql = 'SELECT value FROM ' . $this->_cacheTable . ' WHERE itemkey=\'' . $key . '\' AND (expire=0 OR expire>' . time() . ') ORDER BY expire DESC';
476
		$command = $this->getDbConnection()->createCommand($sql);
477
		try {
478
			return unserialize($command->queryScalar());
479
		} catch (\Exception $e) {
480
			$this->initializeCache(true);
481
			return unserialize($command->queryScalar());
482
		}
483
	}
484
485
	/**
486
	 * Stores a value identified by a key in cache.
487
	 * This is the implementation of the method declared in the parent class.
488
	 *
489
	 * @param string $key the key identifying the value to be cached
490
	 * @param string $value the value to be cached
491
	 * @param int $expire the number of seconds in which the cached value will expire. 0 means never expire.
492
	 * @return bool true if the value is successfully stored into cache, false otherwise
493
	 */
494
	protected function setValue($key, $value, $expire)
495
	{
496
		if (!$this->_cacheInitialized) {
497
			$this->initializeCache();
498
		}
499
		$db = $this->getDbConnection();
500
		$driver = $db->getDriverName();
501
		if (in_array($driver, ['mysql', 'mysqli', 'sqlite', 'ibm', 'oci', 'sqlsrv', 'mssql', 'dblib', 'pgsql'])) {
502
			$expire = ($expire <= 0) ? 0 : time() + $expire;
503
			if (in_array($driver, ['mysql', 'mysqli', 'sqlite'])) {
504
				$sql = "REPLACE INTO {$this->_cacheTable} (itemkey,value,expire) VALUES (:key,:value,$expire)";
505
			} elseif ($driver === 'pgsql') {
506
				$sql = "INSERT INTO {$this->_cacheTable} (itemkey, value, expire) VALUES (:key, :value, :expire) " .
507
					"ON CONFLICT (itemkey) DO UPDATE SET value = EXCLUDED.value, expire = EXCLUDED.expire";
508
			} else {
509
				$sql = "MERGE INTO {$this->_cacheTable} AS c " .
510
				"USING (SELECT :key AS itemkey, :value AS value, $expire AS expire) AS data " .
511
				"ON c.itemkey = data.itemkey " .
512
				"WHEN MATCHED THEN " .
513
					"UPDATE SET c.value = data.value, c.expire = data.expire " .
514
				"WHEN NOT MATCHED THEN " .
515
					"INSERT (itemkey, value, expire) " .
516
					"VALUES (data.itemkey, data.value, data.expire)";
517
			}
518
			$command = $db->createCommand($sql);
519
			$command->bindValue(':key', $key, \PDO::PARAM_STR);
520
			$command->bindValue(':value', serialize($value), \PDO::PARAM_LOB);
521
522
			try {
523
				$command->execute();
524
				return true;
525
			} catch (\Exception $e) {
526
				try {
527
					$this->initializeCache(true);
528
					$command->execute();
529
					return true;
530
				} catch (\Exception $e) {
531
					return false;
532
				}
533
			}
534
		} else {
535
			$isCurrentTransaction = $this->getDbConnection()->getCurrentTransaction();
536
			$transaction = $this->getDbConnection()->getCurrentTransaction() ?? $this->getDbConnection()->beginTransaction();
537
538
			$this->deleteValue($key);
539
			$return = $this->addValue($key, $value, $expire);
540
541
			if (!$isCurrentTransaction) {
0 ignored issues
show
$isCurrentTransaction is of type Prado\Data\TDbTransaction, thus it always evaluated to true.
Loading history...
542
				$transaction->commit();
543
			}
544
545
			return $return;
546
		}
547
	}
548
549
	/**
550
	 * Stores a value identified by a key into cache if the cache does not contain this key.
551
	 * This is the implementation of the method declared in the parent class.
552
	 *
553
	 * @param string $key the key identifying the value to be cached
554
	 * @param string $value the value to be cached
555
	 * @param int $expire the number of seconds in which the cached value will expire. 0 means never expire.
556
	 * @return bool true if the value is successfully stored into cache, false otherwise
557
	 */
558
	protected function addValue($key, $value, $expire)
559
	{
560
		if (!$this->_cacheInitialized) {
561
			$this->initializeCache();
562
		}
563
		$expire = ($expire <= 0) ? 0 : time() + $expire;
564
		$sql = "INSERT INTO {$this->_cacheTable} (itemkey,value,expire) VALUES(:key,:value,$expire)";
565
		$command = $this->getDbConnection()->createCommand($sql);
566
		$command->bindValue(':key', $key, \PDO::PARAM_STR);
567
		$command->bindValue(':value', serialize($value), \PDO::PARAM_LOB);
568
569
		try {
570
			$command->execute();
571
			return true;
572
		} catch (\Exception $e) {
573
			try {
574
				$this->initializeCache(true);
575
				$command->execute();
576
				return true;
577
			} catch (\Exception $e) {
578
				return false;
579
			}
580
		}
581
	}
582
583
	/**
584
	 * Deletes a value with the specified key from cache
585
	 * This is the implementation of the method declared in the parent class.
586
	 * @param string $key the key of the value to be deleted
587
	 * @return bool if no error happens during deletion
588
	 */
589
	protected function deleteValue($key)
590
	{
591
		if (!$this->_cacheInitialized) {
592
			$this->initializeCache();
593
		}
594
595
		$command = $this->getDbConnection()->createCommand("DELETE FROM {$this->_cacheTable} WHERE itemkey=:key");
596
		$command->bindValue(':key', $key, \PDO::PARAM_STR);
597
		try {
598
			$command->execute();
599
			return true;
600
		} catch (\Exception $e) {
601
			$this->initializeCache(true);
602
			$command->execute();
603
			return true;
604
		}
605
	}
606
607
	/**
608
	 * Deletes all values from cache.
609
	 * Be careful of performing this operation if the cache is shared by multiple applications.
610
	 * @return bool if no error happens during flush
611
	 */
612
	public function flush()
613
	{
614
		if (!$this->_cacheInitialized) {
615
			$this->initializeCache();
616
		}
617
		$command = $this->getDbConnection()->createCommand("DELETE FROM {$this->_cacheTable}");
618
		try {
619
			$command->execute();
620
		} catch (\Exception $e) {
621
			try {
622
				$this->initializeCache(true);
623
				$command->execute();
624
				return true;
625
			} catch (\Exception $e) {
626
				return false;
627
			}
628
		}
629
		return true;
630
	}
631
}
632