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

/fuel/core/classes/cache/storage/apc.php

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