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
|
|||||||
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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() 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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
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
|
|||||||
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 |
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 theid
property of an instance of theAccount
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.