PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/recache/RECacheManager.php

http://github.com/TomFrost/Hydrogen
PHP | 321 lines | 277 code | 33 blank | 11 comment | 51 complexity | 208b57030e2956a1e4a95004341be98b MD5 | raw file
  1. <?php
  2. /*
  3. * Copyright (c) 2009 - 2012, Frosted Design
  4. * All rights reserved.
  5. */
  6. namespace hydrogen\recache;
  7. use hydrogen\config\Config;
  8. use hydrogen\cache\CacheEngineFactory;
  9. use hydrogen\semaphore\SemaphoreEngineFactory;
  10. use hydrogen\recache\AssetWrapper;
  11. use hydrogen\recache\RECacheWrapper;
  12. use hydrogen\recache\TimeQueue;
  13. class RECacheManager {
  14. const RECACHE_FAIL = 0;
  15. const RECACHE_SUCCESS = 1;
  16. const RECACHE_UPDATE = 2;
  17. protected static $manager = NULL;
  18. protected $engine, $semengine, $trackHits, $recacheWaiting, $pageCacheData;
  19. protected function __construct($trackHits=true) {
  20. $this->engine = CacheEngineFactory::getEngine();
  21. $this->semengine = SemaphoreEngineFactory::getEngine();
  22. $this->recacheWaiting = array();
  23. $this->trackHits = $trackHits;
  24. }
  25. public static function getInstance($trackHits=true) {
  26. if (is_null(static::$manager))
  27. static::$manager = new RECacheManager($trackHits);
  28. return static::$manager;
  29. }
  30. public function get($key, $allow_expired=false) {
  31. $ukey = $this->uniqueKey('ITEM', $key);
  32. $wrap = $this->engine->get($ukey);
  33. if ($wrap === false || !($wrap instanceof AssetWrapper)) {
  34. $this->get_miss();
  35. return false;
  36. }
  37. if (!$allow_expired) {
  38. if ($wrap->expire && $wrap->expire <= microtime(true)) {
  39. $this->get_miss();
  40. return false;
  41. }
  42. foreach ($wrap->groups as $group) {
  43. $gtime = $this->engine->get($this->uniqueKey('GROUP', $group));
  44. if ($gtime && $wrap->time <= $gtime) {
  45. $this->get_miss();
  46. return false;
  47. }
  48. }
  49. }
  50. $this->get_hit();
  51. return $wrap->data;
  52. }
  53. public function set($key, $value, $ttl=1800, $groups=false) {
  54. $wrap = new AssetWrapper($value, $ttl, $groups);
  55. $ukey = $this->uniqueKey('ITEM', $key);
  56. if ($this->engine->set($ukey, $wrap, $ttl + 30)) {
  57. if (($idx = array_search($key, $this->recacheWaiting)) !== false) {
  58. $this->semengine->release($this->uniqueKey('RECACHE', $key));
  59. unset($this->recacheWaiting[$idx]);
  60. }
  61. $this->set_hit();
  62. return true;
  63. }
  64. return false;
  65. }
  66. public function checkIfFrequent($num, $seconds, $key) {
  67. $tqkey = $this->uniqueKey('TQ', "${key}_${num}_${seconds}");
  68. $this->semengine->acquire($tqkey);
  69. $tq = $this->engine->get($tqkey);
  70. if (!$tq)
  71. $tq = new TimeQueue($num, $seconds);
  72. $isFrequent = $tq->hit();
  73. $this->engine->set($tqkey, $tq, $seconds + 5);
  74. $this->semengine->release($tqkey);
  75. return $isFrequent;
  76. }
  77. public function setIfFrequent($num, $seconds, $key, $value, $ttl=7200, $group=false) {
  78. $set = false;
  79. if ($this->checkIfFrequent($num, $seconds, $key))
  80. $set = $this->set($key, $value, $ttl, $group);
  81. if (($idx = array_search($key, $this->recacheWaiting)) !== false) {
  82. $this->semengine->release($this->uniqueKey('RECACHE', $key));
  83. unset($this->recacheWaiting[$idx]);
  84. }
  85. return $set;
  86. }
  87. public function increment($key, $value=1) {
  88. $success = $this->engine->increment($this->uniqueKey('ITEM', $key), $value);
  89. if ($success === false) {
  90. $success = $this->engine->set($this->uniqueKey('ITEM', $key), $value);
  91. if ($success)
  92. $success = $value;
  93. }
  94. return $success;
  95. }
  96. public function decrement($key, $value=1) {
  97. $success = $this->engine->decrement($this->uniqueKey('ITEM', $key), $value);
  98. if ($success === false) {
  99. $success = $this->engine->set($this->uniqueKey('ITEM', $key), 0);
  100. if ($success)
  101. $success = 0;
  102. }
  103. return $success;
  104. }
  105. public function clear($key) {
  106. return $this->engine->delete($this->uniqueKey('ITEM', $key));
  107. }
  108. public function clearGroup($group) {
  109. $key = $this->uniqueKey('GROUP', $group);
  110. $time = round(microtime(true), 4);
  111. return $this->engine->set($key, $time, 0);
  112. }
  113. public function clearAll() {
  114. return $this->engine->deleteAll();
  115. }
  116. public function getStats($addToStats=false) {
  117. $stats = $this->engine->getStats();
  118. if (!$stats)
  119. $stats = array();
  120. else if ($stats && !is_array($stats))
  121. $stats = array($stats);
  122. if ($addToStats !== false && !is_array($addToStats))
  123. $addToStats = array($addToStats);
  124. if ($this->trackHits) {
  125. $gethits = $this->engine->get($this->uniqueKey('STATS', 'get_hits'));
  126. $getmisses = $this->engine->get($this->uniqueKey('STATS', 'get_misses'));
  127. $sethits = $this->engine->get($this->uniqueKey('STATS', 'set_hits'));
  128. $stats['recache_get_hits'] = $gethits === false ? 0 : $gethits;
  129. $stats['recache_get_misses'] = $getmisses === false ? 0 : $getmisses;
  130. $stats['recache_set_hits'] = $sethits === false ? 0 : $sethits;
  131. }
  132. if (is_array($addToStats)) {
  133. foreach ($addToStats as $stat) {
  134. $value = $this->engine->get($this->uniqueKey('ITEM', $stat));
  135. if (!is_null($value) && isset($value->data))
  136. $stats[$stat] = $value->data;
  137. }
  138. }
  139. return $stats;
  140. }
  141. public function pageCache($ttl=1800, $key=false, $groups=false, $num=false, $seconds=false) {
  142. if (!$key) {
  143. $key = "http://".$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
  144. if (count($_POST) > 0)
  145. $key .= serialize($_POST);
  146. }
  147. $pageContent = $this->get($key);
  148. if ($pageContent) {
  149. die($pageContent);
  150. }
  151. $this->pageCacheData = array(
  152. 'ttl' => $ttl,
  153. 'key' => $key,
  154. 'groups' => $groups,
  155. 'num' => $num,
  156. 'seconds' => $seconds
  157. );
  158. ob_start(array($this, 'endPageCache'));
  159. }
  160. public function endPageCache($pageContent) {
  161. if (!is_array($this->pageCacheData))
  162. return '';
  163. if ($this->pageCacheData['num'] && $this->pageCacheData['seconds'])
  164. $this->setIfFrequent($this->pageCacheData['num'], $this->pageCacheData['seconds'], $this->pageCacheData['key'],
  165. $pageContent, $this->pageCacheData['ttl'], $this->pageCacheData['groups']);
  166. else
  167. $this->set($this->pageCacheData['key'], $pageContent, $this->pageCacheData['ttl'], $this->pageCacheData['groups']);
  168. $this->pageCacheData = false;
  169. return $pageContent;
  170. }
  171. public function recache_pageCache($ttl=1800, $key=false, $groups=false, $failFunction=false) {
  172. if (!$key) {
  173. $key = "http://".$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
  174. if (count($_POST) > 0)
  175. $key .= serialize($_POST);
  176. }
  177. $wrap = $this->recache_get_wrapper($key);
  178. switch ($wrap->response) {
  179. case self::RECACHE_SUCCESS:
  180. die($wrap->data);
  181. case self::RECACHE_UPDATE:
  182. $this->pageCacheData = array(
  183. 'ttl' => $ttl,
  184. 'key' => $key,
  185. 'groups' => $groups,
  186. 'num' => false,
  187. 'seconds' => false
  188. );
  189. ob_start(array($this, 'endPageCache'));
  190. break;
  191. case self::RECACHE_FAIL:
  192. if (!$failFunction)
  193. $val = call_user_func($failFunction);
  194. else
  195. die();
  196. }
  197. }
  198. public function recache_get($key, $ttl, $groups, $updateFunction,
  199. $updateFuncArgs=array(), $failFunction=false, $failFuncArgs=array()) {
  200. $wrap = $this->recache_get_wrapper($key);
  201. switch ($wrap->response) {
  202. case self::RECACHE_SUCCESS:
  203. return $wrap->data;
  204. case self::RECACHE_UPDATE:
  205. $val = call_user_func_array($updateFunction, $updateFuncArgs);
  206. $this->set($key, $val, $ttl, $groups);
  207. return $val;
  208. case self::RECACHE_FAIL:
  209. if ($failFunction)
  210. $val = call_user_func_array($failFunction, $failFuncArgs);
  211. else
  212. die('Server busy. Please try again later.');
  213. }
  214. return NULL;
  215. }
  216. public function recache_get_wrapper($key) {
  217. // First we make the wrapper
  218. $wrap = new RECacheWrapper();
  219. // Get the request. If the cache has it, return it with SUCCESS.
  220. $val = $this->get($key);
  221. if ($val !== false) {
  222. $wrap->response = self::RECACHE_SUCCESS;
  223. $wrap->data = $val;
  224. return $wrap;
  225. }
  226. // The cache doesn't have it. If it's not currently being updated, send the UPDATE response.
  227. $semkey = $this->uniqueKey('RECACHE', $key);
  228. if ($this->semengine->acquire($semkey, 0)) {
  229. $this->recacheWaiting[] = $key;
  230. $wrap->response = self::RECACHE_UPDATE;
  231. return $wrap;
  232. }
  233. // It's currently being updated. If we have old data available, use that for now.
  234. $val = $this->get($key, true);
  235. if ($val !== false) {
  236. $wrap->response = self::RECACHE_SUCCESS;
  237. $wrap->data = $val;
  238. return $wrap;
  239. }
  240. // We don't have old data. Wait for the key to be updated, and return the new data.
  241. if ($this->semengine->acquire($semkey)) {
  242. $this->semengine->release($semkey);
  243. $val = $this->get($key, true);
  244. if ($val !== false) {
  245. $wrap->response = self::RECACHE_SUCCESS;
  246. $wrap->data = $val;
  247. return $wrap;
  248. }
  249. }
  250. // The server still hasn't handled the update, meaning we're probably at risk of an overload.
  251. // Recommend an immediate die() so we don't crash out.
  252. $wrap->response = self::RECACHE_FAIL;
  253. return $wrap;
  254. }
  255. public function recache_cancel($key) {
  256. if (($idx = array_search($key, $this->recacheWaiting)) !== false) {
  257. $this->semengine->release($this->uniqueKey('RECACHE', $key));
  258. unset($this->recacheWaiting[$idx]);
  259. return true;
  260. }
  261. return false;
  262. }
  263. protected function uniqueKey($type, $key) {
  264. return Config::getRequiredVal('recache', 'unique_name') . ':' . $type . ':' . $key;
  265. }
  266. protected function get_miss() {
  267. if ($this->trackHits) {
  268. $key = $this->uniqueKey('STATS', 'get_misses');
  269. if (!$this->engine->increment($key, 1))
  270. $this->engine->set($key, 1, 0);
  271. }
  272. }
  273. protected function get_hit() {
  274. if ($this->trackHits) {
  275. $key = $this->uniqueKey('STATS', 'get_hits');
  276. if (!$this->engine->increment($key, 1))
  277. $this->engine->set($key, 1, 0);
  278. }
  279. }
  280. protected function set_hit() {
  281. if ($this->trackHits) {
  282. $key = $this->uniqueKey('STATS', 'set_hits');
  283. if (!$this->engine->increment($key, 1))
  284. $this->engine->set($key, 1, 0);
  285. }
  286. }
  287. }
  288. ?>