PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Associates/Smarty/sysplugins/smarty_cacheresource_keyvaluestore.php

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