PageRenderTime 55ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://bitbucket.org/codeyash/bootstrap
PHP | 490 lines | 299 code | 60 blank | 131 comment | 29 complexity | a90656f0b1255c38620c860fcdf979d0 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_Redis 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. * @var Redis storage for the redis object
  25. */
  26. protected $redis = false;
  27. public function __construct($identifier, $config)
  28. {
  29. parent::__construct($identifier, $config);
  30. $this->config = isset($config['redis']) ? $config['redis'] : array();
  31. // make sure we have a redis id
  32. $this->config['cache_id'] = $this->_validate_config('cache_id', isset($this->config['cache_id'])
  33. ? $this->config['cache_id'] : 'fuel');
  34. // check for an expiration override
  35. $this->expiration = $this->_validate_config('expiration', isset($this->config['expiration'])
  36. ? $this->config['expiration'] : $this->expiration);
  37. // make sure we have a redis database configured
  38. $this->config['database'] = $this->_validate_config('database', isset($this->config['database'])
  39. ? $this->config['database'] : 'default');
  40. if ($this->redis === false)
  41. {
  42. // get the redis database instance
  43. try
  44. {
  45. $this->redis = \Redis::instance($this->config['database']);
  46. }
  47. catch (\Exception $e)
  48. {
  49. throw new \FuelException('Can not connect to the Redis engine. The error message says "'.$e->getMessage().'".');
  50. }
  51. // get the redis version
  52. preg_match('/redis_version:(.*?)\n/', $this->redis->info(), $info);
  53. if (version_compare(trim($info[1]), '1.2') < 0)
  54. {
  55. throw new \FuelException('Version 1.2 or higher of the Redis NoSQL engine is required to use the redis cache driver.');
  56. }
  57. }
  58. }
  59. /**
  60. * Translates a given identifier to a valid redis key
  61. *
  62. * @param string
  63. * @return string
  64. */
  65. protected function identifier_to_key( $identifier )
  66. {
  67. return $this->config['cache_id'].':'.$identifier;
  68. }
  69. /**
  70. * Prepend the cache properties
  71. *
  72. * @return string
  73. */
  74. protected function prep_contents()
  75. {
  76. $properties = array(
  77. 'created' => $this->created,
  78. 'expiration' => $this->expiration,
  79. 'dependencies' => $this->dependencies,
  80. 'content_handler' => $this->content_handler
  81. );
  82. $properties = '{{'.static::PROPS_TAG.'}}'.json_encode($properties).'{{/'.static::PROPS_TAG.'}}';
  83. return $properties.$this->contents;
  84. }
  85. /**
  86. * Remove the prepended cache properties and save them in class properties
  87. *
  88. * @param string
  89. * @throws UnexpectedValueException
  90. */
  91. protected function unprep_contents($payload)
  92. {
  93. $properties_end = strpos($payload, '{{/'.static::PROPS_TAG.'}}');
  94. if ($properties_end === FALSE)
  95. {
  96. throw new \UnexpectedValueException('Cache has bad formatting');
  97. }
  98. $this->contents = substr($payload, $properties_end + strlen('{{/'.static::PROPS_TAG.'}}'));
  99. $props = substr(substr($payload, 0, $properties_end), strlen('{{'.static::PROPS_TAG.'}}'));
  100. $props = json_decode($props, true);
  101. if ($props === NULL)
  102. {
  103. throw new \UnexpectedValueException('Cache properties retrieval failed');
  104. }
  105. $this->created = $props['created'];
  106. $this->expiration = is_null($props['expiration']) ? null : (int) ($props['expiration'] - time());
  107. $this->dependencies = $props['dependencies'];
  108. $this->content_handler = $props['content_handler'];
  109. }
  110. /**
  111. * Check if other caches or files have been changed since cache creation
  112. *
  113. * @param array
  114. * @return bool
  115. */
  116. public function check_dependencies(array $dependencies)
  117. {
  118. foreach($dependencies as $dep)
  119. {
  120. // get the section name and identifier
  121. $sections = explode('.', $dep);
  122. if (count($sections) > 1)
  123. {
  124. $identifier = array_pop($sections);
  125. $sections = '.'.implode('.', $sections);
  126. }
  127. else
  128. {
  129. $identifier = $dep;
  130. $sections = '';
  131. }
  132. // get the cache index
  133. $index = $this->redis->get($this->config['cache_id'].':index:'.$sections);
  134. is_null($index) or $index = $this->_unserialize($index);
  135. // get the key from the index
  136. $key = isset($index[$identifier][0]) ? $index[$identifier] : false;
  137. // key found and newer?
  138. if ($key === false or $key[1] > $this->created)
  139. {
  140. return false;
  141. }
  142. }
  143. return true;
  144. }
  145. /**
  146. * Delete Cache
  147. */
  148. public function delete()
  149. {
  150. // get the key for the cache identifier
  151. $key = $this->_get_key(true);
  152. // delete the key from the redis server
  153. if ($key and $this->redis->del($key) === false)
  154. {
  155. // do something here?
  156. }
  157. $this->reset();
  158. }
  159. /**
  160. * Purge all caches
  161. *
  162. * @param limit purge to subsection
  163. * @return bool
  164. */
  165. public function delete_all($section)
  166. {
  167. // determine the section index name
  168. $section = empty($section) ? '' : '.'.$section;
  169. // get the directory index
  170. $index = $this->redis->get($this->config['cache_id'].':dir:');
  171. is_null($index) or $index = $this->_unserialize($index);
  172. if (is_array($index))
  173. {
  174. if (!empty($section))
  175. {
  176. // limit the delete if we have a valid section
  177. $dirs = array();
  178. foreach ($index as $entry)
  179. {
  180. if ($entry == $section or strpos($entry, $section.'.') === 0)
  181. {
  182. $dirs[] = $entry;
  183. }
  184. }
  185. }
  186. else
  187. {
  188. // else delete the entire contents of the cache
  189. $dirs = $index;
  190. }
  191. // loop through the selected indexes
  192. foreach ($dirs as $dir)
  193. {
  194. // get the stored cache entries for this index
  195. $list = $this->redis->get($this->config['cache_id'].':index:'.$dir);
  196. if (is_null($list))
  197. {
  198. $list = array();
  199. }
  200. else
  201. {
  202. $list = $this->_unserialize($list);
  203. }
  204. // delete all stored keys
  205. foreach($list as $item)
  206. {
  207. $this->redis->del($item[0]);
  208. }
  209. // and delete the index itself
  210. $this->redis->del($this->config['cache_id'].':index:'.$dir);
  211. }
  212. // update the directory index
  213. $this->redis->set($this->config['cache_id'].':dir:', $this->_serialize(array_diff($index, $dirs)));
  214. }
  215. }
  216. /**
  217. * Save a cache, this does the generic pre-processing
  218. *
  219. * @return bool success
  220. */
  221. protected function _set()
  222. {
  223. // get the key for the cache identifier
  224. $key = $this->_get_key();
  225. // write the cache
  226. $this->redis->set($key, $this->prep_contents());
  227. if ( ! empty($this->expiration))
  228. {
  229. $this->redis->expireat($key, $this->expiration);
  230. }
  231. return true;
  232. }
  233. /**
  234. * Load a cache, this does the generic post-processing
  235. *
  236. * @return bool success
  237. */
  238. protected function _get()
  239. {
  240. // get the key for the cache identifier
  241. $key = $this->_get_key();
  242. // fetch the session data from the redis server
  243. $payload = $this->redis->get($key);
  244. try
  245. {
  246. $this->unprep_contents($payload);
  247. }
  248. catch (\UnexpectedValueException $e)
  249. {
  250. return false;
  251. }
  252. return true;
  253. }
  254. /**
  255. * get's the memcached key belonging to the cache identifier
  256. *
  257. * @param bool if true, remove the key retrieved from the index
  258. * @return string
  259. */
  260. private function _get_key($remove = false)
  261. {
  262. // get the section name and identifier
  263. $sections = explode('.', $this->identifier);
  264. if (count($sections) > 1)
  265. {
  266. $identifier = array_pop($sections);
  267. $sections = '.'.implode('.', $sections);
  268. }
  269. else
  270. {
  271. $identifier = $this->identifier;
  272. $sections = '';
  273. }
  274. // get the cache index
  275. $index = $this->redis->get($this->config['cache_id'].':index:'.$sections);
  276. is_null($index) or $index = $this->_unserialize($index);
  277. // get the key from the index
  278. $key = isset($index[$identifier][0]) ? $index[$identifier][0] : false;
  279. if ($remove === true)
  280. {
  281. if ( $key !== false )
  282. {
  283. unset($index[$identifier]);
  284. $this->redis->set($this->config['cache_id'].':index:'.$sections, $this->_serialize($index));
  285. }
  286. }
  287. else
  288. {
  289. if ( $key === false )
  290. {
  291. // create a new key
  292. $key = $this->_new_key();
  293. if ( ! is_array($index))
  294. {
  295. // create a new index and store the key
  296. $this->redis->set($this->config['cache_id'].':index:'.$sections, $this->_serialize(array($identifier => array($key,$this->created))));
  297. }
  298. else
  299. {
  300. // add the key to the index
  301. $index[$identifier] = array($key,$this->created);
  302. $this->redis->set($this->config['cache_id'].':index:'.$sections, $this->_serialize($index));
  303. }
  304. // get the directory index
  305. $index = $this->redis->get($this->config['cache_id'].':dir:');
  306. is_null($index) or $index = $this->_unserialize($index);
  307. if (is_array($index))
  308. {
  309. if ( ! in_array($sections, $index))
  310. {
  311. $index[] = $sections;
  312. }
  313. }
  314. else
  315. {
  316. $index = array($sections);
  317. }
  318. // update the directory index
  319. $this->redis->set($this->config['cache_id'].':dir:', $this->_serialize($index));
  320. }
  321. }
  322. return $key;
  323. }
  324. /**
  325. * generate a new unique key for the current identifier
  326. *
  327. * @return string
  328. */
  329. private function _new_key()
  330. {
  331. $key = '';
  332. while (strlen($key) < 32)
  333. {
  334. $key .= mt_rand(0, mt_getrandmax());
  335. }
  336. return $this->config['cache_id'].'_'.uniqid($key);
  337. }
  338. /**
  339. * validate a driver config value
  340. *
  341. * @param string name of the config variable to validate
  342. * @param mixed value
  343. * @return mixed
  344. */
  345. private function _validate_config($name, $value)
  346. {
  347. switch ($name)
  348. {
  349. case 'database':
  350. // do we have a database config
  351. if (empty($value) or ! is_array($value))
  352. {
  353. $value = 'default';
  354. }
  355. break;
  356. case 'cache_id':
  357. if (empty($value) or ! is_string($value))
  358. {
  359. $value = 'fuel';
  360. }
  361. break;
  362. case 'expiration':
  363. if (empty($value) or ! is_numeric($value))
  364. {
  365. $value = null;
  366. }
  367. break;
  368. default:
  369. break;
  370. }
  371. return $value;
  372. }
  373. /**
  374. * Serialize an array
  375. *
  376. * This function first converts any slashes found in the array to a temporary
  377. * marker, so when it gets unserialized the slashes will be preserved
  378. *
  379. * @param array
  380. * @return string
  381. */
  382. protected function _serialize($data)
  383. {
  384. if (is_array($data))
  385. {
  386. foreach ($data as $key => $val)
  387. {
  388. if (is_string($val))
  389. {
  390. $data[$key] = str_replace('\\', '{{slash}}', $val);
  391. }
  392. }
  393. }
  394. else
  395. {
  396. if (is_string($data))
  397. {
  398. $data = str_replace('\\', '{{slash}}', $data);
  399. }
  400. }
  401. return serialize($data);
  402. }
  403. /**
  404. * Unserialize
  405. *
  406. * This function unserializes a data string, then converts any
  407. * temporary slash markers back to actual slashes
  408. *
  409. * @param array
  410. * @return string
  411. */
  412. protected function _unserialize($data)
  413. {
  414. $data = @unserialize(stripslashes($data));
  415. if (is_array($data))
  416. {
  417. foreach ($data as $key => $val)
  418. {
  419. if (is_string($val))
  420. {
  421. $data[$key] = str_replace('{{slash}}', '\\', $val);
  422. }
  423. }
  424. return $data;
  425. }
  426. return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
  427. }
  428. }