PageRenderTime 53ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/codeyash/bootstrap
PHP | 412 lines | 246 code | 58 blank | 108 comment | 27 complexity | f68d209405d360300abdc526ef3aa762 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_Memcached 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 Memcached storage for the memcached object
  25. */
  26. protected $memcached = false;
  27. public function __construct($identifier, $config)
  28. {
  29. parent::__construct($identifier, $config);
  30. $this->config = isset($config['memcached']) ? $config['memcached'] : array();
  31. // make sure we have a memcache 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. if ($this->memcached === false)
  38. {
  39. // make sure we have memcached servers configured
  40. $this->config['servers'] = $this->_validate_config('servers', $this->config['servers']);
  41. // do we have the PHP memcached extension available
  42. if ( ! class_exists('Memcached') )
  43. {
  44. throw new \FuelException('Memcached cache are configured, but your PHP installation doesn\'t have the Memcached extension loaded.');
  45. }
  46. // instantiate the memcached object
  47. $this->memcached = new \Memcached();
  48. // add the configured servers
  49. $this->memcached->addServers($this->config['servers']);
  50. // check if we can connect to the server(s)
  51. if ($this->memcached->getVersion() === false)
  52. {
  53. throw new \FuelException('Memcached cache are configured, but there is no connection possible. Check your configuration.');
  54. }
  55. }
  56. }
  57. /**
  58. * Prepend the cache properties
  59. *
  60. * @return string
  61. */
  62. protected function prep_contents()
  63. {
  64. $properties = array(
  65. 'created' => $this->created,
  66. 'expiration' => $this->expiration,
  67. 'dependencies' => $this->dependencies,
  68. 'content_handler' => $this->content_handler
  69. );
  70. $properties = '{{'.static::PROPS_TAG.'}}'.json_encode($properties).'{{/'.static::PROPS_TAG.'}}';
  71. return $properties.$this->contents;
  72. }
  73. /**
  74. * Remove the prepended cache properties and save them in class properties
  75. *
  76. * @param string
  77. * @throws UnexpectedValueException
  78. */
  79. protected function unprep_contents($payload)
  80. {
  81. $properties_end = strpos($payload, '{{/'.static::PROPS_TAG.'}}');
  82. if ($properties_end === FALSE)
  83. {
  84. throw new \UnexpectedValueException('Cache has bad formatting');
  85. }
  86. $this->contents = substr($payload, $properties_end + strlen('{{/'.static::PROPS_TAG.'}}'));
  87. $props = substr(substr($payload, 0, $properties_end), strlen('{{'.static::PROPS_TAG.'}}'));
  88. $props = json_decode($props, true);
  89. if ($props === NULL)
  90. {
  91. throw new \UnexpectedValueException('Cache properties retrieval failed');
  92. }
  93. $this->created = $props['created'];
  94. $this->expiration = is_null($props['expiration']) ? null : (int) ($props['expiration'] - time());
  95. $this->dependencies = $props['dependencies'];
  96. $this->content_handler = $props['content_handler'];
  97. }
  98. /**
  99. * Check if other caches or files have been changed since cache creation
  100. *
  101. * @param array
  102. * @return bool
  103. */
  104. public function check_dependencies(array $dependencies)
  105. {
  106. foreach($dependencies as $dep)
  107. {
  108. // get the section name and identifier
  109. $sections = explode('.', $dep);
  110. if (count($sections) > 1)
  111. {
  112. $identifier = array_pop($sections);
  113. $sections = '.'.implode('.', $sections);
  114. }
  115. else
  116. {
  117. $identifier = $dep;
  118. $sections = '';
  119. }
  120. // get the cache index
  121. $index = $this->memcached->get($this->config['cache_id'].$sections);
  122. // get the key from the index
  123. $key = isset($index[$identifier][0]) ? $index[$identifier] : false;
  124. // key found and newer?
  125. if ($key === false or $key[1] > $this->created)
  126. {
  127. return false;
  128. }
  129. }
  130. return true;
  131. }
  132. /**
  133. * Delete Cache
  134. */
  135. public function delete()
  136. {
  137. // get the memcached key for the cache identifier
  138. $key = $this->_get_key(true);
  139. // delete the key from the memcached server
  140. if ($key and $this->memcached->delete($key) === false)
  141. {
  142. if ($this->memcached->getResultCode() !== \Memcached::RES_NOTFOUND)
  143. {
  144. throw new \FuelException('Memcached returned error code "'.$this->memcached->getResultCode().'" on delete. Check your configuration.');
  145. }
  146. }
  147. $this->reset();
  148. }
  149. /**
  150. * Purge all caches
  151. *
  152. * @param limit purge to subsection
  153. * @return bool
  154. */
  155. public function delete_all($section)
  156. {
  157. // determine the section index name
  158. $section = $this->config['cache_id'].(empty($section)?'':'.'.$section);
  159. // get the directory index
  160. $index = $this->memcached->get($this->config['cache_id'].'__DIR__');
  161. if (is_array($index))
  162. {
  163. // limit the delete if we have a valid section
  164. if ( ! empty($section))
  165. {
  166. $dirs = in_array($section, $index) ? array($section) : array();
  167. }
  168. else
  169. {
  170. $dirs = $index;
  171. }
  172. // loop through the indexes, delete all stored keys, then delete the indexes
  173. foreach ($dirs as $dir)
  174. {
  175. $list = $this->memcached->get($dir);
  176. foreach ($list as $item)
  177. {
  178. $this->memcached->delete($item[0]);
  179. }
  180. $this->memcached->delete($dir);
  181. }
  182. // update the directory index
  183. $index = array_diff($index, $dirs);
  184. $this->memcached->set($this->config['cache_id'].'__DIR__', $index);
  185. }
  186. }
  187. /**
  188. * Save a cache, this does the generic pre-processing
  189. *
  190. * @return bool success
  191. */
  192. protected function _set()
  193. {
  194. // get the memcached key for the cache identifier
  195. $key = $this->_get_key();
  196. $payload = $this->prep_contents();
  197. // write it to the memcached server
  198. if ($this->memcached->set($key, $payload, ! is_null($this->expiration) ? (int) $this->expiration : 0) === false)
  199. {
  200. throw new \FuelException('Memcached returned error code "'.$this->memcached->getResultCode().'" on write. Check your configuration.');
  201. }
  202. return true;
  203. }
  204. /**
  205. * Load a cache, this does the generic post-processing
  206. *
  207. * @return bool success
  208. */
  209. protected function _get()
  210. {
  211. // get the memcached key for the cache identifier
  212. $key = $this->_get_key();
  213. // fetch the cached data from the Memcached server
  214. $payload = $this->memcached->get($key);
  215. try
  216. {
  217. $this->unprep_contents($payload);
  218. }
  219. catch (\UnexpectedValueException $e)
  220. {
  221. return false;
  222. }
  223. return true;
  224. }
  225. /**
  226. * validate a driver config value
  227. *
  228. * @param string name of the config variable to validate
  229. * @param mixed value
  230. * @return mixed
  231. */
  232. private function _validate_config($name, $value)
  233. {
  234. switch ($name)
  235. {
  236. case 'cache_id':
  237. if (empty($value) or ! is_string($value))
  238. {
  239. $value = 'fuel';
  240. }
  241. break;
  242. case 'expiration':
  243. if (empty($value) or ! is_numeric($value))
  244. {
  245. $value = null;
  246. }
  247. break;
  248. case 'servers':
  249. // do we have a servers config
  250. if ( empty($value) OR ! is_array($value))
  251. {
  252. $value = array('default' => array('host' => '127.0.0.1', 'port' => '11211'));
  253. }
  254. // validate the servers
  255. foreach ($value as $key => $server)
  256. {
  257. // do we have a host?
  258. if ( ! isset($server['host']) OR ! is_string($server['host']))
  259. {
  260. throw new \FuelException('Invalid Memcached server definition in the cache configuration.');
  261. }
  262. // do we have a port number?
  263. if ( ! isset($server['port']) OR ! is_numeric($server['port']) OR $server['port'] < 1025 OR $server['port'] > 65535)
  264. {
  265. throw new \FuelException('Invalid Memcached server definition in the cache configuration.');
  266. }
  267. // do we have a relative server weight?
  268. if ( ! isset($server['weight']) OR ! is_numeric($server['weight']) OR $server['weight'] < 0)
  269. {
  270. // set a default
  271. $value[$key]['weight'] = 0;
  272. }
  273. }
  274. break;
  275. default:
  276. break;
  277. }
  278. return $value;
  279. }
  280. /**
  281. * Get's the memcached key belonging to the cache identifier
  282. *
  283. * @param bool if true, remove the key retrieved from the index
  284. * @return string
  285. */
  286. private function _get_key($remove = false)
  287. {
  288. // get the section name and identifier
  289. $sections = explode('.', $this->identifier);
  290. if (count($sections) > 1)
  291. {
  292. $identifier = array_pop($sections);
  293. $sections = '.'.implode('.', $sections);
  294. }
  295. else
  296. {
  297. $identifier = $this->identifier;
  298. $sections = '';
  299. }
  300. // get the cache index
  301. $index = $this->memcached->get($this->config['cache_id'].$sections);
  302. // get the key from the index
  303. $key = isset($index[$identifier][0]) ? $index[$identifier][0] : false;
  304. if ($remove === true)
  305. {
  306. if ( $key !== false )
  307. {
  308. unset($index[$identifier]);
  309. $this->memcached->set($this->config['cache_id'].$sections, $index);
  310. }
  311. }
  312. else
  313. {
  314. if ( $key === false )
  315. {
  316. // create a new key
  317. $key = $this->_new_key();
  318. // create a new index and store the key
  319. is_array($index) || $index = array();
  320. $this->memcached->set($this->config['cache_id'].$sections, array_merge($index, array($identifier => array($key,$this->created))), 0);
  321. // get the directory index
  322. $index = $this->memcached->get($this->config['cache_id'].'__DIR__');
  323. if (is_array($index))
  324. {
  325. if (!in_array($this->config['cache_id'].$sections, $index))
  326. {
  327. $index[] = $this->config['cache_id'].$sections;
  328. }
  329. }
  330. else
  331. {
  332. $index = array($this->config['cache_id'].$sections);
  333. }
  334. // update the directory index
  335. $this->memcached->set($this->config['cache_id'].'__DIR__', $index, 0);
  336. }
  337. }
  338. return $key;
  339. }
  340. /**
  341. * Generate a new unique key for the current identifier
  342. *
  343. * @return string
  344. */
  345. private function _new_key()
  346. {
  347. $key = '';
  348. while (strlen($key) < 32)
  349. {
  350. $key .= mt_rand(0, mt_getrandmax());
  351. }
  352. return md5($this->config['cache_id'].'_'.uniqid($key, TRUE));
  353. }
  354. }