PageRenderTime 54ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/sources/subs/Cache.subs.php

https://github.com/Arantor/Elkarte
PHP | 469 lines | 296 code | 46 blank | 127 comment | 114 complexity | d9668120318b852f54ccfa4c08263a23 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. /**
  3. * @name ElkArte Forum
  4. * @copyright ElkArte Forum contributors
  5. * @license BSD http://opensource.org/licenses/BSD-3-Clause
  6. *
  7. * This software is a derived product, based on:
  8. *
  9. * Simple Machines Forum (SMF)
  10. * copyright: 2011 Simple Machines (http://www.simplemachines.org)
  11. * license: BSD, See included LICENSE.TXT for terms and conditions.
  12. *
  13. * @version 1.0 Alpha
  14. *
  15. * This file has the hefty job of loading information for the forum.
  16. *
  17. */
  18. if (!defined('ELKARTE'))
  19. die('No access...');
  20. /**
  21. * Try to retrieve a cache entry. On failure, call the appropriate function.
  22. *
  23. * @param string $key
  24. * @param string $file
  25. * @param string $function
  26. * @param array $params
  27. * @param int $level = 1
  28. * @return string
  29. */
  30. function cache_quick_get($key, $file, $function, $params, $level = 1)
  31. {
  32. global $modSettings;
  33. // @todo Why are we doing this if caching is disabled?
  34. if (function_exists('call_integration_hook'))
  35. call_integration_hook('pre_cache_quick_get', array($key, $file, $function, $params, $level));
  36. /* Refresh the cache if either:
  37. 1. Caching is disabled.
  38. 2. The cache level isn't high enough.
  39. 3. The item has not been cached or the cached item expired.
  40. 4. The cached item has a custom expiration condition evaluating to true.
  41. 5. The expire time set in the cache item has passed (needed for Zend).
  42. */
  43. if (empty($modSettings['cache_enable']) || $modSettings['cache_enable'] < $level || !is_array($cache_block = cache_get_data($key, 3600)) || (!empty($cache_block['refresh_eval']) && eval($cache_block['refresh_eval'])) || (!empty($cache_block['expires']) && $cache_block['expires'] < time()))
  44. {
  45. require_once(SOURCEDIR . '/' . $file);
  46. $cache_block = call_user_func_array($function, $params);
  47. if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= $level)
  48. cache_put_data($key, $cache_block, $cache_block['expires'] - time());
  49. }
  50. // Some cached data may need a freshening up after retrieval.
  51. if (!empty($cache_block['post_retri_eval']))
  52. eval($cache_block['post_retri_eval']);
  53. if (function_exists('call_integration_hook'))
  54. call_integration_hook('post_cache_quick_get', array($cache_block));
  55. return $cache_block['data'];
  56. }
  57. /**
  58. * Puts value in the cache under key for ttl seconds.
  59. *
  60. * - It may "miss" so shouldn't be depended on
  61. * - Uses the cache engine chosen in the ACP and saved in settings.php
  62. * - It supports:
  63. * Turck MMCache: http://turck-mmcache.sourceforge.net/index_old.html#api
  64. * Xcache: http://xcache.lighttpd.net/wiki/XcacheApi
  65. * memcache: http://www.php.net/memcache
  66. * APC: http://www.php.net/apc
  67. * eAccelerator: http://bart.eaccelerator.net/doc/phpdoc/
  68. * Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
  69. * Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
  70. *
  71. * @param string $key
  72. * @param mixed $value
  73. * @param int $ttl = 120
  74. */
  75. function cache_put_data($key, $value, $ttl = 120)
  76. {
  77. global $boardurl, $modSettings, $memcached;
  78. global $cache_hits, $cache_count, $db_show_debug;
  79. global $cache_accelerator, $cache_enable;
  80. if (empty($cache_enable))
  81. return;
  82. $cache_count = isset($cache_count) ? $cache_count + 1 : 1;
  83. if (isset($db_show_debug) && $db_show_debug === true)
  84. {
  85. $cache_hits[$cache_count] = array('k' => $key, 'd' => 'put', 's' => $value === null ? 0 : strlen(serialize($value)));
  86. $st = microtime(true);
  87. }
  88. $key = cache_get_key($key);
  89. $value = $value === null ? null : serialize($value);
  90. switch ($cache_accelerator)
  91. {
  92. case 'memcached':
  93. // The simple yet efficient memcached.
  94. if (function_exists('memcached_set') || function_exists('memcache_set') && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '')
  95. {
  96. // Not connected yet?
  97. if (empty($memcached))
  98. get_memcached_server();
  99. if (!$memcached)
  100. return;
  101. memcache_set($memcached, $key, $value, 0, $ttl);
  102. }
  103. break;
  104. case 'eaccelerator':
  105. // eAccelerator...
  106. if (function_exists('eaccelerator_put'))
  107. {
  108. if (mt_rand(0, 10) == 1)
  109. eaccelerator_gc();
  110. if ($value === null)
  111. @eaccelerator_rm($key);
  112. else
  113. eaccelerator_put($key, $value, $ttl);
  114. }
  115. break;
  116. case 'mmcache':
  117. // Turck MMCache?
  118. if (function_exists('mmcache_put'))
  119. {
  120. if (mt_rand(0, 10) == 1)
  121. mmcache_gc();
  122. if ($value === null)
  123. @mmcache_rm($key);
  124. else
  125. {
  126. mmcache_lock($key);
  127. mmcache_put($key, $value, $ttl);
  128. mmcache_unlock($key);
  129. }
  130. }
  131. break;
  132. case 'apc':
  133. // Alternative PHP Cache, ahoy!
  134. if (function_exists('apc_store'))
  135. {
  136. // An extended key is needed to counteract a bug in APC.
  137. if ($value === null)
  138. apc_delete($key . 'elkarte');
  139. else
  140. apc_store($key . 'elkarte', $value, $ttl);
  141. }
  142. break;
  143. case 'zend':
  144. // Zend Platform/ZPS/etc.
  145. if (function_exists('zend_shm_cache_store'))
  146. zend_shm_cache_store('ELKARTE::' . $key, $value, $ttl);
  147. elseif (function_exists('output_cache_put'))
  148. output_cache_put($key, $value);
  149. break;
  150. case 'xcache':
  151. if (function_exists('xcache_set') && ini_get('xcache.var_size') > 0)
  152. {
  153. if ($value === null)
  154. xcache_unset($key);
  155. else
  156. xcache_set($key, $value, $ttl);
  157. }
  158. break;
  159. default:
  160. // Otherwise custom cache?
  161. if ($value === null)
  162. @unlink(CACHEDIR . '/data_' . $key . '.php');
  163. else
  164. {
  165. $cache_data = '<' . '?' . 'php if (!defined(\'ELKARTE\')) die; if (' . (time() + $ttl) . ' < time()) $expired = true; else{$expired = false; $value = \'' . addcslashes($value, '\\\'') . '\';}';
  166. // Write out the cache file, check that the cache write was successful; all the data must be written
  167. // If it fails due to low diskspace, or other, remove the cache file
  168. if (file_put_contents(CACHEDIR . '/data_' . $key . '.php', $cache_data, LOCK_EX) !== strlen($cache_data))
  169. @unlink(CACHEDIR . '/data_' . $key . '.php');
  170. }
  171. break;
  172. }
  173. if (function_exists('call_integration_hook'))
  174. call_integration_hook('cache_put_data', array($key, $value, $ttl));
  175. if (isset($db_show_debug) && $db_show_debug === true)
  176. $cache_hits[$cache_count]['t'] = microtime(true) - $st;
  177. }
  178. /**
  179. * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
  180. * - It may often "miss", so shouldn't be depended on.
  181. * - It supports the same as cache_put_data().
  182. *
  183. * @param string $key
  184. * @param int $ttl = 120
  185. * @return string
  186. */
  187. function cache_get_data($key, $ttl = 120)
  188. {
  189. global $boardurl, $modSettings, $memcached;
  190. global $cache_hits, $cache_count, $db_show_debug;
  191. global $cache_accelerator, $cache_enable;
  192. if (empty($cache_enable))
  193. return;
  194. $cache_count = isset($cache_count) ? $cache_count + 1 : 1;
  195. if (isset($db_show_debug) && $db_show_debug === true)
  196. {
  197. $cache_hits[$cache_count] = array(
  198. 'k' => $key,
  199. 'd' => 'get'
  200. );
  201. $st = microtime(true);
  202. }
  203. $key = cache_get_key($key);
  204. switch ($cache_accelerator)
  205. {
  206. case 'memcache':
  207. // Okay, let's go for it memcached!
  208. if ((function_exists('memcache_get') || function_exists('memcached_get')) && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '')
  209. {
  210. // Not connected yet?
  211. if (empty($memcached))
  212. get_memcached_server();
  213. if (!$memcached)
  214. return null;
  215. $value = (function_exists('memcache_get')) ? memcache_get($cache['connection'], $key) : memcached_get($cache['connection'], $key);
  216. }
  217. break;
  218. case 'eaccelerator':
  219. // Again, eAccelerator.
  220. if (function_exists('eaccelerator_get'))
  221. $value = eaccelerator_get($key);
  222. break;
  223. case 'mmcache':
  224. // The older, but ever-stable, Turck MMCache...
  225. if (function_exists('mmcache_get'))
  226. $value = mmcache_get($key);
  227. break;
  228. case 'apc':
  229. // This is the free APC from PECL.
  230. if (function_exists('apc_fetch'))
  231. $value = apc_fetch($key . 'elkarte');
  232. break;
  233. case 'zend':
  234. // Zend's pricey stuff.
  235. if (function_exists('zend_shm_cache_fetch'))
  236. $value = zend_shm_cache_fetch('ELKARTE::' . $key, $ttl);
  237. elseif (function_exists('output_cache_get'))
  238. $value = output_cache_get($key, $ttl);
  239. break;
  240. case 'xcache':
  241. if (function_exists('xcache_get') && ini_get('xcache.var_size') > 0)
  242. $value = xcache_get($key);
  243. break;
  244. default:
  245. // Otherwise it's Elkarte data!
  246. if (file_exists(CACHEDIR . '/data_' . $key . '.php') && filesize(CACHEDIR . '/data_' . $key . '.php') > 10)
  247. {
  248. // php will cache file_exists et all, we can't 100% depend on its results so proceed with caution
  249. @include(CACHEDIR . '/data_' . $key . '.php');
  250. if (!empty($expired) && isset($value))
  251. {
  252. @unlink(CACHEDIR . '/data_' . $key . '.php');
  253. unset($value);
  254. }
  255. }
  256. break;
  257. }
  258. if (isset($db_show_debug) && $db_show_debug === true)
  259. {
  260. $cache_hits[$cache_count]['t'] = microtime(true) - $st;
  261. $cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0;
  262. }
  263. if (function_exists('call_integration_hook') && isset($value))
  264. call_integration_hook('cache_get_data', array($key, $ttl, $value));
  265. return empty($value) ? null : @unserialize($value);
  266. }
  267. /**
  268. * Get memcache servers.
  269. *
  270. * - This function is used by cache_get_data() and cache_put_data().
  271. * - It attempts to connect to a random server in the cache_memcached setting.
  272. * - It recursively calls itself up to $level times.
  273. *
  274. * @param int $level = 3
  275. */
  276. function get_memcached_server($level = 3)
  277. {
  278. global $modSettings, $memcached, $db_persist, $cache_memcached;
  279. $servers = explode(',', $cache_memcached);
  280. $server = explode(':', trim($servers[array_rand($servers)]));
  281. $cache = (function_exists('memcache_get')) ? 'memcache' : ((function_exists('memcached_get') ? 'memcached' : ''));
  282. // Don't try more times than we have servers!
  283. $level = min(count($servers), $level);
  284. // Don't wait too long: yes, we want the server, but we might be able to run the query faster!
  285. if (empty($db_persist))
  286. {
  287. if ($cache === 'memcached')
  288. $memcached = memcached_connect($server[0], empty($server[1]) ? 11211 : $server[1]);
  289. if ($cache === 'memcache')
  290. $memcached = memcache_connect($server[0], empty($server[1]) ? 11211 : $server[1]);
  291. }
  292. else
  293. {
  294. if ($cache === 'memcached')
  295. $memcached = memcached_pconnect($server[0], empty($server[1]) ? 11211 : $server[1]);
  296. if ($cache === 'memcache')
  297. $memcached = memcache_pconnect($server[0], empty($server[1]) ? 11211 : $server[1]);
  298. }
  299. if (!$memcached && $level > 0)
  300. get_memcached_server($level - 1);
  301. }
  302. /**
  303. * Empty out the cache in use as best it can
  304. *
  305. * It may only remove the files of a certain type (if the $type parameter is given)
  306. * Type can be user, data or left blank
  307. * - user clears out user data
  308. * - data clears out system / opcode data
  309. * - If no type is specified will perfom a complete cache clearing
  310. * For cache engines that do not distinguish on types, a full cache flush will be done
  311. *
  312. * @param string $type = ''
  313. */
  314. function clean_cache($type = '')
  315. {
  316. global $cache_accelerator, $modSettings, $memcached;
  317. switch ($cache_accelerator)
  318. {
  319. case 'memcached':
  320. if (function_exists('memcache_flush') || function_exists('memcached_flush') && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '')
  321. {
  322. // Not connected yet?
  323. if (empty($memcached))
  324. get_memcached_server();
  325. if (!$memcached)
  326. return;
  327. // clear it out
  328. if (function_exists('memcache_flush'))
  329. memcache_flush($memcached);
  330. else
  331. memcached_flush($memcached);
  332. }
  333. break;
  334. case 'eaccelerator':
  335. if (function_exists('eaccelerator_clear') && function_exists('eaccelerator_clean') )
  336. {
  337. // Clean out the already expired items
  338. @eaccelerator_clean();
  339. // Remove all unused scripts and data from shared memory and disk cache,
  340. // e.g. all data that isn't used in the current requests.
  341. @eaccelerator_clear();
  342. }
  343. case 'mmcache':
  344. if (function_exists('mmcache_gc'))
  345. {
  346. // removes all expired keys from shared memory, this is not a complete cache flush :(
  347. // @todo there is no clear function, should we try to find all of the keys and delete those? with mmcache_rm
  348. mmcache_gc();
  349. }
  350. break;
  351. case 'apc':
  352. if (function_exists('apc_clear_cache'))
  353. {
  354. // if passed a type, clear that type out
  355. if ($type === '' || $type === 'data')
  356. {
  357. apc_clear_cache('user');
  358. apc_clear_cache('system');
  359. }
  360. elseif ($type === 'user')
  361. apc_clear_cache('user');
  362. }
  363. break;
  364. case 'zend':
  365. if (function_exists('zend_shm_cache_clear'))
  366. zend_shm_cache_clear('ELKARTE');
  367. break;
  368. case 'xcache':
  369. if (function_exists('xcache_clear_cache') && function_exists('xcache_count'))
  370. {
  371. // @todo interface !!!
  372. //$_SERVER["PHP_AUTH_USER"] = 'userid';
  373. //$_SERVER["PHP_AUTH_PW"] = 'password'; /* not the md5 one in the .ini but the real password */
  374. // Get the counts so we clear each instance
  375. $pcnt = xcache_count(XC_TYPE_PHP);
  376. $vcnt = xcache_count(XC_TYPE_VAR);
  377. // Time to clear the user vars and/or the opcache
  378. if ($type === '' || $type === 'user')
  379. {
  380. for ($i = 0; $i < $vcnt; $i++)
  381. xcache_clear_cache(XC_TYPE_VAR, $i);
  382. }
  383. if ($type === '' || $type === 'data')
  384. {
  385. for ($i = 0; $i < $pcnt; $i++)
  386. xcache_clear_cache(XC_TYPE_PHP, $i);
  387. }
  388. }
  389. break;
  390. default:
  391. // No directory = no game.
  392. if (!is_dir(CACHEDIR))
  393. return;
  394. // Remove the files in our own disk cache, if any
  395. $dh = opendir(CACHEDIR);
  396. while ($file = readdir($dh))
  397. {
  398. if ($file != '.' && $file != '..' && $file != 'index.php' && $file != '.htaccess' && (!$type || substr($file, 0, strlen($type)) == $type))
  399. @unlink(CACHEDIR . '/' . $file);
  400. }
  401. closedir($dh);
  402. break;
  403. }
  404. // Invalidate cache, to be sure!
  405. // ... as long as Load.php can be modified, anyway.
  406. @touch(SOURCEDIR . '/' . 'Load.php');
  407. clearstatcache();
  408. }
  409. /**
  410. * Get the key for the cache.
  411. *
  412. * @param string $key
  413. * @return string $key
  414. */
  415. function cache_get_key($key)
  416. {
  417. global $boardurl, $cache_accelerator;
  418. static $key_prefix;
  419. // no need to do this every time, slows us down :P
  420. if (empty($key_prefix))
  421. $key_prefix = md5($boardurl . filemtime(SOURCEDIR . '/Load.php')) . '-ELKARTE-';
  422. return $key_prefix . ((empty($cache_accelerator) || $cache_accelerator === 'filebased') ? strtr($key, ':/', '-_') : $key);
  423. }