smarty_cacheresource_keyvaluestore.php (17663B)
1 <?php 2 /** 3 * Smarty Internal Plugin 4 * 5 * @package Smarty 6 * @subpackage Cacher 7 */ 8 9 /** 10 * Smarty Cache Handler Base for Key/Value Storage Implementations 11 * This class implements the functionality required to use simple key/value stores 12 * for hierarchical cache groups. key/value stores like memcache or APC do not support 13 * wildcards in keys, therefore a cache group cannot be cleared like "a|*" - which 14 * is no problem to filesystem and RDBMS implementations. 15 * This implementation is based on the concept of invalidation. While one specific cache 16 * can be identified and cleared, any range of caches cannot be identified. For this reason 17 * each level of the cache group hierarchy can have its own value in the store. These values 18 * are nothing but microtimes, telling us when a particular cache group was cleared for the 19 * last time. These keys are evaluated for every cache read to determine if the cache has 20 * been invalidated since it was created and should hence be treated as inexistent. 21 * Although deep hierarchies are possible, they are not recommended. Try to keep your 22 * cache groups as shallow as possible. Anything up 3-5 parents should be ok. So 23 * »a|b|c« is a good depth where »a|b|c|d|e|f|g|h|i|j|k« isn't. Try to join correlating 24 * cache groups: if your cache groups look somewhat like »a|b|$page|$items|$whatever« 25 * consider using »a|b|c|$page-$items-$whatever« instead. 26 * 27 * @package Smarty 28 * @subpackage Cacher 29 * @author Rodney Rehm 30 */ 31 abstract class Smarty_CacheResource_KeyValueStore extends Smarty_CacheResource 32 { 33 /** 34 * cache for contents 35 * 36 * @var array 37 */ 38 protected $contents = array(); 39 40 /** 41 * cache for timestamps 42 * 43 * @var array 44 */ 45 protected $timestamps = array(); 46 47 /** 48 * populate Cached Object with meta data from Resource 49 * 50 * @param Smarty_Template_Cached $cached cached object 51 * @param Smarty_Internal_Template $_template template object 52 * 53 * @return void 54 */ 55 public function populate(Smarty_Template_Cached $cached, Smarty_Internal_Template $_template) 56 { 57 $cached->filepath = $_template->source->uid . '#' . $this->sanitize($cached->source->resource) . '#' . $this->sanitize($cached->cache_id) . '#' . $this->sanitize($cached->compile_id); 58 59 $this->populateTimestamp($cached); 60 } 61 62 /** 63 * populate Cached Object with timestamp and exists from Resource 64 * 65 * @param Smarty_Template_Cached $cached cached object 66 * 67 * @return void 68 */ 69 public function populateTimestamp(Smarty_Template_Cached $cached) 70 { 71 if (!$this->fetch($cached->filepath, $cached->source->name, $cached->cache_id, $cached->compile_id, $content, $timestamp, $cached->source->uid)) { 72 return; 73 } 74 $cached->content = $content; 75 $cached->timestamp = (int) $timestamp; 76 $cached->exists = $cached->timestamp; 77 } 78 79 /** 80 * Read the cached template and process the header 81 * 82 * @param Smarty_Internal_Template $_template template object 83 * @param Smarty_Template_Cached $cached cached object 84 * 85 * @return boolean true or false if the cached content does not exist 86 */ 87 public function process(Smarty_Internal_Template $_template, Smarty_Template_Cached $cached = null) 88 { 89 if (!$cached) { 90 $cached = $_template->cached; 91 } 92 $content = $cached->content ? $cached->content : null; 93 $timestamp = $cached->timestamp ? $cached->timestamp : null; 94 if ($content === null || !$timestamp) { 95 if (!$this->fetch($_template->cached->filepath, $_template->source->name, $_template->cache_id, $_template->compile_id, $content, $timestamp, $_template->source->uid)) { 96 return false; 97 } 98 } 99 if (isset($content)) { 100 /** @var Smarty_Internal_Template $_smarty_tpl 101 * used in evaluated code 102 */ 103 $_smarty_tpl = $_template; 104 eval("?>" . $content); 105 106 return true; 107 } 108 109 return false; 110 } 111 112 /** 113 * Write the rendered template output to cache 114 * 115 * @param Smarty_Internal_Template $_template template object 116 * @param string $content content to cache 117 * 118 * @return boolean success 119 */ 120 public function writeCachedContent(Smarty_Internal_Template $_template, $content) 121 { 122 $this->addMetaTimestamp($content); 123 124 return $this->write(array($_template->cached->filepath => $content), $_template->properties['cache_lifetime']); 125 } 126 127 /** 128 * Read cached template from cache 129 * 130 * @param Smarty_Internal_Template $_template template object 131 * 132 * @return string content 133 */ 134 public function readCachedContent(Smarty_Internal_Template $_template) 135 { 136 $content = $_template->cached->content ? $_template->cached->content : null; 137 $timestamp = null; 138 if ($content === null) { 139 if (!$this->fetch($_template->cached->filepath, $_template->source->name, $_template->cache_id, $_template->compile_id, $content, $timestamp, $_template->source->uid)) { 140 return false; 141 } 142 } 143 if (isset($content)) { 144 return $content; 145 } 146 return false; 147 } 148 149 /** 150 * Empty cache 151 * {@internal the $exp_time argument is ignored altogether }} 152 * 153 * @param Smarty $smarty Smarty object 154 * @param integer $exp_time expiration time [being ignored] 155 * 156 * @return integer number of cache files deleted [always -1] 157 * @uses purge() to clear the whole store 158 * @uses invalidate() to mark everything outdated if purge() is inapplicable 159 */ 160 public function clearAll(Smarty $smarty, $exp_time = null) 161 { 162 if (!$this->purge()) { 163 $this->invalidate(null); 164 } 165 166 return - 1; 167 } 168 169 /** 170 * Empty cache for a specific template 171 * {@internal the $exp_time argument is ignored altogether}} 172 * 173 * @param Smarty $smarty Smarty object 174 * @param string $resource_name template name 175 * @param string $cache_id cache id 176 * @param string $compile_id compile id 177 * @param integer $exp_time expiration time [being ignored] 178 * 179 * @return integer number of cache files deleted [always -1] 180 * @uses buildCachedFilepath() to generate the CacheID 181 * @uses invalidate() to mark CacheIDs parent chain as outdated 182 * @uses delete() to remove CacheID from cache 183 */ 184 public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time) 185 { 186 $uid = $this->getTemplateUid($smarty, $resource_name, $cache_id, $compile_id); 187 $cid = $uid . '#' . $this->sanitize($resource_name) . '#' . $this->sanitize($cache_id) . '#' . $this->sanitize($compile_id); 188 $this->delete(array($cid)); 189 $this->invalidate($cid, $resource_name, $cache_id, $compile_id, $uid); 190 191 return - 1; 192 } 193 194 /** 195 * Get template's unique ID 196 * 197 * @param Smarty $smarty Smarty object 198 * @param string $resource_name template name 199 * @param string $cache_id cache id 200 * @param string $compile_id compile id 201 * 202 * @return string filepath of cache file 203 */ 204 protected function getTemplateUid(Smarty $smarty, $resource_name, $cache_id, $compile_id) 205 { 206 $uid = ''; 207 if (isset($resource_name)) { 208 $tpl = new $smarty->template_class($resource_name, $smarty); 209 if ($tpl->source->exists) { 210 $uid = $tpl->source->uid; 211 } 212 213 // remove from template cache 214 if ($smarty->allow_ambiguous_resources) { 215 $_templateId = $tpl->source->unique_resource . $tpl->cache_id . $tpl->compile_id; 216 } else { 217 $_templateId = $smarty->joined_template_dir . '#' . $resource_name . $tpl->cache_id . $tpl->compile_id; 218 } 219 if (isset($_templateId[150])) { 220 $_templateId = sha1($_templateId); 221 } 222 unset($smarty->template_objects[$_templateId]); 223 } 224 225 return $uid; 226 } 227 228 /** 229 * Sanitize CacheID components 230 * 231 * @param string $string CacheID component to sanitize 232 * 233 * @return string sanitized CacheID component 234 */ 235 protected function sanitize($string) 236 { 237 // some poeple smoke bad weed 238 $string = trim($string, '|'); 239 if (!$string) { 240 return null; 241 } 242 243 return preg_replace('#[^\w\|]+#S', '_', $string); 244 } 245 246 /** 247 * Fetch and prepare a cache object. 248 * 249 * @param string $cid CacheID to fetch 250 * @param string $resource_name template name 251 * @param string $cache_id cache id 252 * @param string $compile_id compile id 253 * @param string $content cached content 254 * @param integer &$timestamp cached timestamp (epoch) 255 * @param string $resource_uid resource's uid 256 * 257 * @return boolean success 258 */ 259 protected function fetch($cid, $resource_name = null, $cache_id = null, $compile_id = null, &$content = null, &$timestamp = null, $resource_uid = null) 260 { 261 $t = $this->read(array($cid)); 262 $content = !empty($t[$cid]) ? $t[$cid] : null; 263 $timestamp = null; 264 265 if ($content && ($timestamp = $this->getMetaTimestamp($content))) { 266 $invalidated = $this->getLatestInvalidationTimestamp($cid, $resource_name, $cache_id, $compile_id, $resource_uid); 267 if ($invalidated > $timestamp) { 268 $timestamp = null; 269 $content = null; 270 } 271 } 272 273 return !!$content; 274 } 275 276 /** 277 * Add current microtime to the beginning of $cache_content 278 * {@internal the header uses 8 Bytes, the first 4 Bytes are the seconds, the second 4 Bytes are the microseconds}} 279 * 280 * @param string &$content the content to be cached 281 */ 282 protected function addMetaTimestamp(&$content) 283 { 284 $mt = explode(" ", microtime()); 285 $ts = pack("NN", $mt[1], (int) ($mt[0] * 100000000)); 286 $content = $ts . $content; 287 } 288 289 /** 290 * Extract the timestamp the $content was cached 291 * 292 * @param string &$content the cached content 293 * 294 * @return float the microtime the content was cached 295 */ 296 protected function getMetaTimestamp(&$content) 297 { 298 extract(unpack('N1s/N1m/a*content', $content)); 299 return $s + ($m / 100000000); 300 } 301 302 /** 303 * Invalidate CacheID 304 * 305 * @param string $cid CacheID 306 * @param string $resource_name template name 307 * @param string $cache_id cache id 308 * @param string $compile_id compile id 309 * @param string $resource_uid source's uid 310 * 311 * @return void 312 */ 313 protected function invalidate($cid = null, $resource_name = null, $cache_id = null, $compile_id = null, $resource_uid = null) 314 { 315 $now = microtime(true); 316 $key = null; 317 // invalidate everything 318 if (!$resource_name && !$cache_id && !$compile_id) { 319 $key = 'IVK#ALL'; 320 } // invalidate all caches by template 321 else { 322 if ($resource_name && !$cache_id && !$compile_id) { 323 $key = 'IVK#TEMPLATE#' . $resource_uid . '#' . $this->sanitize($resource_name); 324 } // invalidate all caches by cache group 325 else { 326 if (!$resource_name && $cache_id && !$compile_id) { 327 $key = 'IVK#CACHE#' . $this->sanitize($cache_id); 328 } // invalidate all caches by compile id 329 else { 330 if (!$resource_name && !$cache_id && $compile_id) { 331 $key = 'IVK#COMPILE#' . $this->sanitize($compile_id); 332 } // invalidate by combination 333 else { 334 $key = 'IVK#CID#' . $cid; 335 } 336 } 337 } 338 } 339 $this->write(array($key => $now)); 340 } 341 342 /** 343 * Determine the latest timestamp known to the invalidation chain 344 * 345 * @param string $cid CacheID to determine latest invalidation timestamp of 346 * @param string $resource_name template name 347 * @param string $cache_id cache id 348 * @param string $compile_id compile id 349 * @param string $resource_uid source's filepath 350 * 351 * @return float the microtime the CacheID was invalidated 352 */ 353 protected function getLatestInvalidationTimestamp($cid, $resource_name = null, $cache_id = null, $compile_id = null, $resource_uid = null) 354 { 355 // abort if there is no CacheID 356 if (false && !$cid) { 357 return 0; 358 } 359 // abort if there are no InvalidationKeys to check 360 if (!($_cid = $this->listInvalidationKeys($cid, $resource_name, $cache_id, $compile_id, $resource_uid))) { 361 return 0; 362 } 363 364 // there are no InValidationKeys 365 if (!($values = $this->read($_cid))) { 366 return 0; 367 } 368 // make sure we're dealing with floats 369 $values = array_map('floatval', $values); 370 371 return max($values); 372 } 373 374 /** 375 * Translate a CacheID into the list of applicable InvalidationKeys. 376 * Splits "some|chain|into|an|array" into array( '#clearAll#', 'some', 'some|chain', 'some|chain|into', ... ) 377 * 378 * @param string $cid CacheID to translate 379 * @param string $resource_name template name 380 * @param string $cache_id cache id 381 * @param string $compile_id compile id 382 * @param string $resource_uid source's filepath 383 * 384 * @return array list of InvalidationKeys 385 * @uses $invalidationKeyPrefix to prepend to each InvalidationKey 386 */ 387 protected function listInvalidationKeys($cid, $resource_name = null, $cache_id = null, $compile_id = null, $resource_uid = null) 388 { 389 $t = array('IVK#ALL'); 390 $_name = $_compile = '#'; 391 if ($resource_name) { 392 $_name .= $resource_uid . '#' . $this->sanitize($resource_name); 393 $t[] = 'IVK#TEMPLATE' . $_name; 394 } 395 if ($compile_id) { 396 $_compile .= $this->sanitize($compile_id); 397 $t[] = 'IVK#COMPILE' . $_compile; 398 } 399 $_name .= '#'; 400 // some poeple smoke bad weed 401 $cid = trim($cache_id, '|'); 402 if (!$cid) { 403 return $t; 404 } 405 $i = 0; 406 while (true) { 407 // determine next delimiter position 408 $i = strpos($cid, '|', $i); 409 // add complete CacheID if there are no more delimiters 410 if ($i === false) { 411 $t[] = 'IVK#CACHE#' . $cid; 412 $t[] = 'IVK#CID' . $_name . $cid . $_compile; 413 $t[] = 'IVK#CID' . $_name . $_compile; 414 break; 415 } 416 $part = substr($cid, 0, $i); 417 // add slice to list 418 $t[] = 'IVK#CACHE#' . $part; 419 $t[] = 'IVK#CID' . $_name . $part . $_compile; 420 // skip past delimiter position 421 $i ++; 422 } 423 424 return $t; 425 } 426 427 /** 428 * Check is cache is locked for this template 429 * 430 * @param Smarty $smarty Smarty object 431 * @param Smarty_Template_Cached $cached cached object 432 * 433 * @return boolean true or false if cache is locked 434 */ 435 public function hasLock(Smarty $smarty, Smarty_Template_Cached $cached) 436 { 437 $key = 'LOCK#' . $cached->filepath; 438 $data = $this->read(array($key)); 439 440 return $data && time() - $data[$key] < $smarty->locking_timeout; 441 } 442 443 /** 444 * Lock cache for this template 445 * 446 * @param Smarty $smarty Smarty object 447 * @param Smarty_Template_Cached $cached cached object 448 * 449 * @return bool|void 450 */ 451 public function acquireLock(Smarty $smarty, Smarty_Template_Cached $cached) 452 { 453 $cached->is_locked = true; 454 $key = 'LOCK#' . $cached->filepath; 455 $this->write(array($key => time()), $smarty->locking_timeout); 456 } 457 458 /** 459 * Unlock cache for this template 460 * 461 * @param Smarty $smarty Smarty object 462 * @param Smarty_Template_Cached $cached cached object 463 * 464 * @return bool|void 465 */ 466 public function releaseLock(Smarty $smarty, Smarty_Template_Cached $cached) 467 { 468 $cached->is_locked = false; 469 $key = 'LOCK#' . $cached->filepath; 470 $this->delete(array($key)); 471 } 472 473 /** 474 * Read values for a set of keys from cache 475 * 476 * @param array $keys list of keys to fetch 477 * 478 * @return array list of values with the given keys used as indexes 479 */ 480 abstract protected function read(array $keys); 481 482 /** 483 * Save values for a set of keys to cache 484 * 485 * @param array $keys list of values to save 486 * @param int $expire expiration time 487 * 488 * @return boolean true on success, false on failure 489 */ 490 abstract protected function write(array $keys, $expire = null); 491 492 /** 493 * Remove values from cache 494 * 495 * @param array $keys list of keys to delete 496 * 497 * @return boolean true on success, false on failure 498 */ 499 abstract protected function delete(array $keys); 500 501 /** 502 * Remove *all* values from cache 503 * 504 * @return boolean true on success, false on failure 505 */ 506 protected function purge() 507 { 508 return false; 509 } 510 }