PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/cache/storage/xcache.php

http://github.com/fuel/core
PHP | 375 lines | 211 code | 54 blank | 110 comment | 17 complexity | 5b84e3436b8105b021fe556e8579d51f MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Fuel is a fast, lightweight, community driven PHP 5.4+ framework.
  4. *
  5. * @package Fuel
  6. * @version 1.9-dev
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2019 Fuel Development Team
  10. * @link https://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. class Cache_Storage_Xcache extends \Cache_Storage_Driver
  14. {
  15. /**
  16. * @const string Tag used for opening & closing cache properties
  17. */
  18. const PROPS_TAG = 'Fuel_Cache_Properties';
  19. /**
  20. * @var array driver specific configuration
  21. */
  22. protected $config = array();
  23. // ---------------------------------------------------------------------
  24. public function __construct($identifier, $config)
  25. {
  26. parent::__construct($identifier, $config);
  27. $this->config = isset($config['xcache']) ? $config['xcache'] : array();
  28. // make sure we have an id
  29. $this->config['cache_id'] = $this->_validate_config('cache_id', isset($this->config['cache_id'])
  30. ? $this->config['cache_id'] : 'fuel');
  31. // check for an expiration override
  32. $this->expiration = $this->_validate_config('expiration', isset($this->config['expiration'])
  33. ? $this->config['expiration'] : $this->expiration);
  34. // do we have the PHP XCache extension available
  35. if ( ! function_exists('xcache_set') )
  36. {
  37. throw new \FuelException('Your PHP installation doesn\'t have XCache loaded.');
  38. }
  39. }
  40. // ---------------------------------------------------------------------
  41. /**
  42. * Check if other caches or files have been changed since cache creation
  43. *
  44. * @param array
  45. * @return bool
  46. */
  47. public function check_dependencies(array $dependencies)
  48. {
  49. foreach($dependencies as $dep)
  50. {
  51. // get the section name and identifier
  52. $sections = explode('.', $dep);
  53. if (count($sections) > 1)
  54. {
  55. $identifier = array_pop($sections);
  56. $sections = '.'.implode('.', $sections);
  57. }
  58. else
  59. {
  60. $identifier = $dep;
  61. $sections = '';
  62. }
  63. // get the cache index
  64. $index = xcache_get($this->config['cache_id'].$sections);
  65. // get the key from the index
  66. $key = isset($index[$identifier][0]) ? $index[$identifier] : false;
  67. // key found and newer?
  68. if ($key === false or $key[1] > $this->created)
  69. {
  70. return false;
  71. }
  72. }
  73. return true;
  74. }
  75. /**
  76. * Delete Cache
  77. */
  78. public function delete()
  79. {
  80. // get the XCache key for the cache identifier
  81. $key = $this->_get_key(true);
  82. // delete the key from the xcache store
  83. $key and xcache_unset($key);
  84. $this->reset();
  85. }
  86. /**
  87. * Purge all caches
  88. *
  89. * @param string $section limit purge to subsection
  90. * @return bool
  91. */
  92. public function delete_all($section)
  93. {
  94. // determine the section index name
  95. $section = $this->config['cache_id'].(empty($section) ? '' : '.'.$section);
  96. // get the directory index
  97. $index = xcache_get($this->config['cache_id'].'__DIR__');
  98. if (is_array($index))
  99. {
  100. $dirs = array();
  101. foreach ($index as $dir)
  102. {
  103. if (strpos($dir, $section) === 0)
  104. {
  105. $dirs[] = $dir;
  106. $list = xcache_get($dir);
  107. foreach ($list as $item)
  108. {
  109. xcache_unset($item[0]);
  110. }
  111. xcache_unset($dir);
  112. }
  113. }
  114. // update the directory index
  115. $dirs and xcache_set($this->config['cache_id'].'__DIR__', array_diff($index, $dirs));
  116. }
  117. }
  118. // ---------------------------------------------------------------------
  119. /**
  120. * Prepend the cache properties
  121. *
  122. * @return string
  123. */
  124. protected function prep_contents()
  125. {
  126. $properties = array(
  127. 'created' => $this->created,
  128. 'expiration' => $this->expiration,
  129. 'dependencies' => $this->dependencies,
  130. 'content_handler' => $this->content_handler,
  131. );
  132. $properties = '{{'.static::PROPS_TAG.'}}'.json_encode($properties).'{{/'.static::PROPS_TAG.'}}';
  133. return $properties.$this->contents;
  134. }
  135. /**
  136. * Remove the prepended cache properties and save them in class properties
  137. *
  138. * @param string $payload
  139. * @throws \UnexpectedValueException
  140. */
  141. protected function unprep_contents($payload)
  142. {
  143. $properties_end = strpos($payload, '{{/'.static::PROPS_TAG.'}}');
  144. if ($properties_end === FALSE)
  145. {
  146. throw new \UnexpectedValueException('Cache has bad formatting');
  147. }
  148. $this->contents = substr($payload, $properties_end + strlen('{{/'.static::PROPS_TAG.'}}'));
  149. $props = substr(substr($payload, 0, $properties_end), strlen('{{'.static::PROPS_TAG.'}}'));
  150. $props = json_decode($props, true);
  151. if ($props === NULL)
  152. {
  153. throw new \UnexpectedValueException('Cache properties retrieval failed');
  154. }
  155. $this->created = $props['created'];
  156. $this->expiration = is_null($props['expiration']) ? null : (int) ($props['expiration'] - time());
  157. $this->dependencies = $props['dependencies'];
  158. $this->content_handler = $props['content_handler'];
  159. }
  160. /**
  161. * Save a cache, this does the generic pre-processing
  162. *
  163. * @return bool success
  164. */
  165. protected function _set()
  166. {
  167. // get the xcache key for the cache identifier
  168. $key = $this->_get_key();
  169. $payload = $this->prep_contents();
  170. // adjust the expiration, xcache uses a TTL instead of a timestamp
  171. $expiration = is_null($this->expiration) ? 0 : (int) ($this->expiration - $this->created);
  172. // write it to the xcache store
  173. if (xcache_set($key, $payload, $expiration) === false)
  174. {
  175. throw new \RuntimeException('Xcache returned failed to write. Check your configuration.');
  176. }
  177. // update the index
  178. $this->_update_index($key);
  179. return true;
  180. }
  181. /**
  182. * Load a cache, this does the generic post-processing
  183. *
  184. * @return bool success
  185. */
  186. protected function _get()
  187. {
  188. // get the xcache key for the cache identifier
  189. $key = $this->_get_key();
  190. // fetch the cached data from the xcache store
  191. $payload = xcache_get($key);
  192. try
  193. {
  194. $this->unprep_contents($payload);
  195. }
  196. catch (\UnexpectedValueException $e)
  197. {
  198. return false;
  199. }
  200. return true;
  201. }
  202. /**
  203. * validate a driver config value
  204. *
  205. * @param string $name name of the config variable to validate
  206. * @param mixed $value
  207. * @return mixed
  208. */
  209. private function _validate_config($name, $value)
  210. {
  211. switch ($name)
  212. {
  213. case 'cache_id':
  214. if (empty($value) or ! is_string($value))
  215. {
  216. $value = 'fuel';
  217. }
  218. break;
  219. case 'expiration':
  220. if (empty($value) or ! is_numeric($value))
  221. {
  222. $value = null;
  223. }
  224. break;
  225. default:
  226. break;
  227. }
  228. return $value;
  229. }
  230. /**
  231. * get's the xcache key belonging to the cache identifier
  232. *
  233. * @param bool $remove if true, remove the key retrieved from the index
  234. * @return string
  235. */
  236. protected function _get_key($remove = false)
  237. {
  238. // get the current index information
  239. list($identifier, $sections, $index) = $this->_get_index();
  240. // get the key from the index
  241. $key = isset($index[$identifier][0]) ? $index[$identifier][0] : false;
  242. if ($remove === true)
  243. {
  244. if ( $key !== false )
  245. {
  246. unset($index[$identifier]);
  247. xcache_set($this->config['cache_id'].$sections, $index);
  248. }
  249. }
  250. else
  251. {
  252. // create a new key if needed
  253. $key === false and $key = $this->_new_key();
  254. }
  255. return $key;
  256. }
  257. /**
  258. * generate a new unique key for the current identifier
  259. *
  260. * @return string
  261. */
  262. protected function _new_key()
  263. {
  264. $key = '';
  265. while (strlen($key) < 32)
  266. {
  267. $key .= mt_rand(0, mt_getrandmax());
  268. }
  269. return md5($this->config['cache_id'].'_'.uniqid($key, TRUE));
  270. }
  271. /**
  272. * Get the section index
  273. *
  274. * @return array containing the identifier, the sections, and the section index
  275. */
  276. protected function _get_index()
  277. {
  278. // get the section name and identifier
  279. $sections = explode('.', $this->identifier);
  280. if (count($sections) > 1)
  281. {
  282. $identifier = array_pop($sections);
  283. $sections = '.'.implode('.', $sections);
  284. }
  285. else
  286. {
  287. $identifier = $this->identifier;
  288. $sections = '';
  289. }
  290. // get the cache index and return it
  291. return array($identifier, $sections, xcache_get($this->config['cache_id'].$sections));
  292. }
  293. /**
  294. * Update the section index
  295. *
  296. * @param string cache key
  297. */
  298. protected function _update_index($key)
  299. {
  300. // get the current index information
  301. list($identifier, $sections, $index) = $this->_get_index();
  302. // store the key in the index and write the index back
  303. $index[$identifier] = array($key, $this->created);
  304. xcache_set($this->config['cache_id'].$sections, array_merge($index, array($identifier => array($key, $this->created))));
  305. // get the directory index
  306. $index = xcache_get($this->config['cache_id'].'__DIR__');
  307. if (is_array($index))
  308. {
  309. if (!in_array($this->config['cache_id'].$sections, $index))
  310. {
  311. $index[] = $this->config['cache_id'].$sections;
  312. }
  313. }
  314. else
  315. {
  316. $index = array($this->config['cache_id'].$sections);
  317. }
  318. // update the directory index
  319. xcache_set($this->config['cache_id'].'__DIR__', $index, 0);
  320. }
  321. }