PageRenderTime 72ms CodeModel.GetById 44ms RepoModel.GetById 1ms app.codeStats 0ms

/test/distributions/smarty/3.1/libs/sysplugins/smarty_cacheresource_keyvaluestore.php

https://github.com/rodneyrehm/php-template-engines
PHP | 393 lines | 169 code | 25 blank | 199 comment | 33 complexity | ef56d5814890fcb6c4effb8a84e84d64 MD5 | raw file
  1. <?php
  2. /**
  3. * Smarty Internal Plugin
  4. *
  5. * @package Smarty
  6. * @subpackage Cacher
  7. */
  8. /**
  9. * Smarty Cache Handler Base for Key/Value Storage Implementations
  10. *
  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. *
  16. * This implementation is based on the concept of invalidation. While one specific cache
  17. * can be identified and cleared, any range of caches cannot be identified. For this reason
  18. * each level of the cache group hierarchy can have its own value in the store. These values
  19. * are nothing but microtimes, telling us when a particular cache group was cleared for the
  20. * last time. These keys are evaluated for every cache read to determine if the cache has
  21. * been invalidated since it was created and should hence be treated as inexistent.
  22. *
  23. * Although deep hierarchies are possible, they are not recommended. Try to keep your
  24. * cache groups as shallow as possible. Anything up 3-5 parents should be ok. So
  25. * »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
  26. * cache groups: if your cache groups look somewhat like »a|b|$page|$items|$whatever«
  27. * consider using »a|b|c|$page-$items-$whatever« instead.
  28. *
  29. * @package Smarty
  30. * @subpackage Cacher
  31. * @author Rodney Rehm
  32. */
  33. abstract class Smarty_CacheResource_KeyValueStore extends Smarty_CacheResource {
  34. /**
  35. * cache for contents
  36. * @var array
  37. */
  38. protected $contents = array();
  39. /**
  40. * cache for timestamps
  41. * @var array
  42. */
  43. protected $timestamps = array();
  44. /**
  45. * populate Cached Object with meta data from Resource
  46. *
  47. * @param Smarty_Template_Cached $cached cached object
  48. * @param Smarty_Internal_Template $_template template object
  49. * @return void
  50. */
  51. public function populate(Smarty_Template_Cached $cached, Smarty_Internal_Template $_template)
  52. {
  53. $cached->filepath = $this->sanitize($cached->source->name)
  54. . '#' . $this->sanitize($cached->cache_id)
  55. . '#' . $this->sanitize($cached->compile_id);
  56. $this->populateTimestamp($cached);
  57. }
  58. /**
  59. * populate Cached Object with timestamp and exists from Resource
  60. *
  61. * @param Smarty_Template_Cached $cached cached object
  62. * @return void
  63. */
  64. public function populateTimestamp(Smarty_Template_Cached $cached)
  65. {
  66. if (!$this->fetch($cached->filepath, $cached->source->name, $cached->cache_id, $cached->compile_id, $content, $timestamp)) {
  67. return;
  68. }
  69. $cached->content = $content;
  70. $cached->timestamp = (int) $timestamp;
  71. $cached->exists = $cached->timestamp;
  72. }
  73. /**
  74. * Read the cached template and process the header
  75. *
  76. * @param Smarty_Internal_Template $_template template object
  77. * @return booelan true or false if the cached content does not exist
  78. */
  79. public function process(Smarty_Internal_Template $_template)
  80. {
  81. $content = '';
  82. $timestamp = null;
  83. if (!$this->fetch($_template->cached->filepath, $_template->source->name, $_template->cache_id, $_template->compile_id, $content, $timestamp)) {
  84. return false;
  85. }
  86. if (isset($content)) {
  87. $_smarty_tpl = $_template;
  88. eval("?>" . $content);
  89. return true;
  90. }
  91. return false;
  92. }
  93. /**
  94. * Write the rendered template output to cache
  95. *
  96. * @param Smarty_Internal_Template $_template template object
  97. * @param string $content content to cache
  98. * @return boolean success
  99. */
  100. public function writeCachedContent(Smarty_Internal_Template $_template, $content)
  101. {
  102. $this->addMetaTimestamp($content);
  103. return $this->write(array($_template->cached->filepath => $content), $_template->properties['cache_lifetime']);
  104. }
  105. /**
  106. * Empty cache
  107. *
  108. * {@internal the $exp_time argument is ignored altogether }}
  109. *
  110. * @param Smarty $smarty Smarty object
  111. * @param integer $exp_time expiration time [being ignored]
  112. * @return integer number of cache files deleted [always -1]
  113. * @uses purge() to clear the whole store
  114. * @uses invalidate() to mark everything outdated if purge() is inapplicable
  115. */
  116. public function clearAll(Smarty $smarty, $exp_time=null)
  117. {
  118. if (!$this->purge()) {
  119. $this->invalidate(null);
  120. }
  121. return -1;
  122. }
  123. /**
  124. * Empty cache for a specific template
  125. *
  126. * {@internal the $exp_time argument is ignored altogether}}
  127. *
  128. * @param Smarty $smarty Smarty object
  129. * @param string $resource_name template name
  130. * @param string $cache_id cache id
  131. * @param string $compile_id compile id
  132. * @param integer $exp_time expiration time [being ignored]
  133. * @return integer number of cache files deleted [always -1]
  134. * @uses buildCachedFilepath() to generate the CacheID
  135. * @uses invalidate() to mark CacheIDs parent chain as outdated
  136. * @uses delete() to remove CacheID from cache
  137. */
  138. public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time)
  139. {
  140. $cid = $this->buildCachedFilepath($resource_name, $cache_id, $compile_id);
  141. $this->delete(array($cid));
  142. $this->invalidate($cid, $resource_name, $cache_id, $compile_id);
  143. return -1;
  144. }
  145. /**
  146. * Get system filepath to cached file.
  147. *
  148. * @param string $resource_name template name
  149. * @param string $cache_id cache id
  150. * @param string $compile_id compile id
  151. * @return string filepath of cache file
  152. * @uses sanitize() on $resource_name and $compile_id to avoid bad segments
  153. */
  154. protected function buildCachedFilepath($resource_name, $cache_id, $compile_id)
  155. {
  156. return $this->sanitize($resource_name) . '#' . $this->sanitize($cache_id) . '#' . $this->sanitize($compile_id);
  157. }
  158. /**
  159. * Sanitize CacheID components
  160. *
  161. * @param string $string CacheID component to sanitize
  162. * @return string sanitized CacheID component
  163. */
  164. protected function sanitize($string)
  165. {
  166. // some poeple smoke bad weed
  167. $string = trim($string, '|');
  168. if (!$string) {
  169. return null;
  170. }
  171. return preg_replace('#[^\w\|]+#S', '_', $string);
  172. }
  173. /**
  174. * Fetch and prepare a cache object.
  175. *
  176. * @param string $cid CacheID to fetch
  177. * @param string $resource_name template name
  178. * @param string $cache_id cache id
  179. * @param string $compile_id compile id
  180. * @param string $content cached content
  181. * @param integer &$timestamp cached timestamp (epoch)
  182. * @return boolean success
  183. */
  184. protected function fetch($cid, $resource_name = null, $cache_id = null, $compile_id = null, &$content = null, &$timestamp = null)
  185. {
  186. $t = $this->read(array($cid));
  187. $content = !empty($t[$cid]) ? $t[$cid] : null;
  188. $timestamp = null;
  189. if ($content && ($timestamp = $this->getMetaTimestamp($content))) {
  190. $invalidated = $this->getLatestInvalidationTimestamp($cid, $resource_name, $cache_id, $compile_id);
  191. if ($invalidated > $timestamp) {
  192. $timestamp = null;
  193. $content = null;
  194. }
  195. }
  196. return !!$content;
  197. }
  198. /**
  199. * Add current microtime to the beginning of $cache_content
  200. *
  201. * {@internal the header uses 8 Bytes, the first 4 Bytes are the seconds, the second 4 Bytes are the microseconds}}
  202. *
  203. * @param string &$content the content to be cached
  204. */
  205. protected function addMetaTimestamp(&$content)
  206. {
  207. $mt = explode(" ", microtime());
  208. $ts = pack("NN", $mt[1], (int) ($mt[0] * 100000000));
  209. $content = $ts . $content;
  210. }
  211. /**
  212. * Extract the timestamp the $content was cached
  213. *
  214. * @param string &$content the cached content
  215. * @return float the microtime the content was cached
  216. */
  217. protected function getMetaTimestamp(&$content)
  218. {
  219. $s = unpack("N", substr($content, 0, 4));
  220. $m = unpack("N", substr($content, 4, 4));
  221. $content = substr($content, 8);
  222. return $s[1] + ($m[1] / 100000000);
  223. }
  224. /**
  225. * Invalidate CacheID
  226. *
  227. * @param string $cid CacheID
  228. * @param string $resource_name template name
  229. * @param string $cache_id cache id
  230. * @param string $compile_id compile id
  231. * @return void
  232. */
  233. protected function invalidate($cid = null, $resource_name = null, $cache_id = null, $compile_id = null)
  234. {
  235. $now = microtime(true);
  236. $key = null;
  237. // invalidate everything
  238. if (!$resource_name && !$cache_id && !$compile_id) {
  239. $key = 'IVK#ALL';
  240. }
  241. // invalidate all caches by template
  242. else if ($resource_name && !$cache_id && !$compile_id) {
  243. $key = 'IVK#TEMPLATE#' . $this->sanitize($resource_name);
  244. }
  245. // invalidate all caches by cache group
  246. else if (!$resource_name && $cache_id && !$compile_id) {
  247. $key = 'IVK#CACHE#' . $this->sanitize($cache_id);
  248. }
  249. // invalidate all caches by compile id
  250. else if (!$resource_name && !$cache_id && $compile_id) {
  251. $key = 'IVK#COMPILE#' . $this->sanitize($compile_id);
  252. }
  253. // invalidate by combination
  254. else {
  255. $key = 'IVK#CID#' . $cid;
  256. }
  257. $this->write(array($key => $now));
  258. }
  259. /**
  260. * Determine the latest timestamp known to the invalidation chain
  261. *
  262. * @param string $cid CacheID to determine latest invalidation timestamp of
  263. * @param string $resource_name template name
  264. * @param string $cache_id cache id
  265. * @param string $compile_id compile id
  266. * @return float the microtime the CacheID was invalidated
  267. */
  268. protected function getLatestInvalidationTimestamp($cid, $resource_name = null, $cache_id = null, $compile_id = null)
  269. {
  270. // abort if there is no CacheID
  271. if (false && !$cid) {
  272. return 0;
  273. }
  274. // abort if there are no InvalidationKeys to check
  275. if (!($_cid = $this->listInvalidationKeys($cid, $resource_name, $cache_id, $compile_id))) {
  276. return 0;
  277. }
  278. // there are no InValidationKeys
  279. if (!($values = $this->read($_cid))) {
  280. return 0;
  281. }
  282. // make sure we're dealing with floats
  283. $values = array_map('floatval', $values);
  284. return max($values);
  285. }
  286. /**
  287. * Translate a CacheID into the list of applicable InvalidationKeys.
  288. *
  289. * Splits "some|chain|into|an|array" into array( '#clearAll#', 'some', 'some|chain', 'some|chain|into', ... )
  290. *
  291. * @param string $cid CacheID to translate
  292. * @param string $resource_name template name
  293. * @param string $cache_id cache id
  294. * @param string $compile_id compile id
  295. * @return array list of InvalidationKeys
  296. * @uses $invalidationKeyPrefix to prepend to each InvalidationKey
  297. */
  298. protected function listInvalidationKeys($cid, $resource_name = null, $cache_id = null, $compile_id = null)
  299. {
  300. $t = array('IVK#ALL');
  301. $_name = $_compile = '#';
  302. if ($resource_name) {
  303. $_name .= $this->sanitize($resource_name);
  304. $t[] = 'IVK#TEMPLATE' . $_name;
  305. }
  306. if ($compile_id) {
  307. $_compile .= $this->sanitize($compile_id);
  308. $t[] = 'IVK#COMPILE' . $_compile;
  309. }
  310. $_name .= '#';
  311. // some poeple smoke bad weed
  312. $cid = trim($cache_id, '|');
  313. if (!$cid) {
  314. return $t;
  315. }
  316. $i = 0;
  317. while (true) {
  318. // determine next delimiter position
  319. $i = strpos($cid, '|', $i);
  320. // add complete CacheID if there are no more delimiters
  321. if ($i === false) {
  322. $t[] = 'IVK#CACHE#' . $cid;
  323. $t[] = 'IVK#CID' . $_name . $cid . $_compile;
  324. $t[] = 'IVK#CID' . $_name . $_compile;
  325. break;
  326. }
  327. $part = substr($cid, 0, $i);
  328. // add slice to list
  329. $t[] = 'IVK#CACHE#' . $part;
  330. $t[] = 'IVK#CID' . $_name . $part . $_compile;
  331. // skip past delimiter position
  332. $i++;
  333. }
  334. return $t;
  335. }
  336. /**
  337. * Read values for a set of keys from cache
  338. *
  339. * @param array $keys list of keys to fetch
  340. * @return array list of values with the given keys used as indexes
  341. */
  342. protected abstract function read(array $keys);
  343. /**
  344. * Save values for a set of keys to cache
  345. *
  346. * @param array $keys list of values to save
  347. * @param int $expire expiration time
  348. * @return boolean true on success, false on failure
  349. */
  350. protected abstract function write(array $keys, $expire=null);
  351. /**
  352. * Remove values from cache
  353. *
  354. * @param array $keys list of keys to delete
  355. * @return boolean true on success, false on failure
  356. */
  357. protected abstract function delete(array $keys);
  358. /**
  359. * Remove *all* values from cache
  360. *
  361. * @return boolean true on success, false on failure
  362. */
  363. protected function purge()
  364. {
  365. return false;
  366. }
  367. }
  368. ?>