PageRenderTime 27ms CodeModel.GetById 37ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/Stash/handlers/Memcached.class.php

https://github.com/gmoulin/lms
PHP | 396 lines | 237 code | 57 blank | 102 comment | 28 complexity | d3eb21dfcd3ef4e1044c7c24965084c5 MD5 | raw file
  1. <?php
  2. /**
  3. * Stash
  4. *
  5. * Copyright (c) 2009-2011, Robert Hafner <tedivm@tedivm.com>.
  6. * All rights reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * * Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * * Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * * Neither the name of Robert Hafner nor the names of his
  21. * contributors may be used to endorse or promote products derived
  22. * from this software without specific prior written permission.
  23. *
  24. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  25. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  26. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  27. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  28. * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  29. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  30. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  31. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  32. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  33. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  34. * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  35. * POSSIBILITY OF SUCH DAMAGE.
  36. *
  37. * @package Stash
  38. * @subpackage Handlers
  39. * @author Robert Hafner <tedivm@tedivm.com>
  40. * @copyright 2009-2011 Robert Hafner <tedivm@tedivm.com>
  41. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  42. * @link http://code.google.com/p/stash/
  43. * @since File available since Release 0.9.1
  44. * @version Release: 0.9.2
  45. */
  46. /**
  47. *
  48. * @package Stash
  49. * @author Robert Hafner <tedivm@tedivm.com>
  50. */
  51. class StashMemcached implements StashHandler
  52. {
  53. /**
  54. * Memcache subhandler used by this class.
  55. *
  56. * @var StashMemcached_Memcached|StashMemcached_Memcache
  57. */
  58. protected $memcached;
  59. /**
  60. *
  61. * * servers - An array of servers, with each server represented by its own array (array(host, port, [weight])). If
  62. * not passed the default is array('127.0.0.1', 11211).
  63. *
  64. * * extension - Which php extension to use, either 'memcached' or 'memcache'. Defaults to memcached with memcache
  65. * as a fallback.
  66. *
  67. * * Options can be passed to the "memcached" handler by adding them to the options array. The memcached extension
  68. * defined options using contants, ie Memcached::OPT_*. By passing in the * portion ('compression' for
  69. * Memcached::OPT_COMPRESSION) and its respective option. Please see the php manual for the specific options
  70. * (http://us2.php.net/manual/en/memcached.constants.php)
  71. *
  72. * @param array $options
  73. */
  74. public function __construct($options = array())
  75. {
  76. if(!isset($options['servers']))
  77. $options['servers'] = array('127.0.0.1', 11211);
  78. if(!is_array($options['servers']))
  79. throw new StashMemcachedError('Server list required to be an array.');
  80. if(is_scalar($options['servers'][0]))
  81. {
  82. $servers = array($options['servers']);
  83. }else{
  84. $servers = $options['servers'];
  85. }
  86. if(!isset($options['extension']))
  87. $options['extension'] = 'any';
  88. $extension = strtolower($options['extension']);
  89. if(class_exists('Memcached', false) && $extension != 'memcache')
  90. {
  91. $this->memcached = new StashMemcached_Memcached();
  92. }elseif(class_exists('Memcache', false) && $extension != 'memcached'){
  93. $this->memcached = new StashMemcached_Memcache();
  94. }else{
  95. throw new StashMemcachedError('Unable to load either memcache extension.');
  96. }
  97. if($this->memcached->initialize($servers, $options))
  98. return;
  99. }
  100. /**
  101. *
  102. * @return array
  103. */
  104. public function getData($key)
  105. {
  106. $keyString = $this->makeKeyString($key);
  107. return $this->memcached->get($keyString);
  108. }
  109. /**
  110. *
  111. * @param array $data
  112. * @param int $expiration
  113. * @return bool
  114. */
  115. public function storeData($key, $data, $expiration)
  116. {
  117. $keyString = $this->makeKeyString($key);
  118. return $this->memcached->set($keyString, $data, $expiration);
  119. }
  120. /**
  121. *
  122. * @param null|array $key
  123. * @return bool
  124. */
  125. public function clear($key = null)
  126. {
  127. $this->keyCache = array();
  128. if(is_null($key))
  129. {
  130. $this->memcached->flush();
  131. }else{
  132. $keyString = $this->makeKeyString($key, true);
  133. $this->memcached->inc($keyString);
  134. $this->keyCache = array();
  135. $this->makeKeyString($key);
  136. }
  137. $this->keyCache = array();
  138. return true;
  139. }
  140. /**
  141. *
  142. * @return bool
  143. */
  144. public function purge()
  145. {
  146. return true;
  147. }
  148. protected function makeKeyString($key, $path = false)
  149. {
  150. // array(name, sub);
  151. // a => name, b => sub;
  152. $key = StashUtilities::normalizeKeys($key);
  153. $keyString = 'cache:::';
  154. foreach($key as $name)
  155. {
  156. //a. cache:::name
  157. //b. cache:::name0:::sub
  158. $keyString .= $name;
  159. //a. :pathdb::cache:::name
  160. //b. :pathdb::cache:::name0:::sub
  161. $pathKey = ':pathdb::' . $keyString;
  162. $pathKey = md5($pathKey);
  163. if(isset($this->keyCache[$pathKey]))
  164. {
  165. $index = $this->keyCache[$pathKey];
  166. }else{
  167. $index = $this->memcached->cas($pathKey, 0);
  168. $this->keyCache[$pathKey] = $index;
  169. }
  170. //a. cache:::name0:::
  171. //b. cache:::name0:::sub1:::
  172. $keyString .= '_' . $index . ':::';
  173. }
  174. return $path ? $pathKey : md5($keyString);
  175. }
  176. /**
  177. *
  178. * @return bool
  179. */
  180. static function canEnable()
  181. {
  182. return class_exists('Memcached', false) || class_exists('Memcache', false);
  183. }
  184. }
  185. class StashMemcached_Memcached
  186. {
  187. protected $memcached;
  188. public function initialize($servers, $options = array())
  189. {
  190. // build this array here instead of as a class variable since the constants are only defined if the extension
  191. // exists
  192. $memOptions = array(
  193. 'COMPRESSION',
  194. 'SERIALIZER',
  195. 'PREFIX_KEY',
  196. 'HASH',
  197. 'DISTRIBUTION',
  198. 'LIBKETAMA_COMPATIBLE',
  199. 'BUFFER_WRITES',
  200. 'BINARY_PROTOCOL',
  201. 'NO_BLOCK',
  202. 'TCP_NODELAY',
  203. 'SOCKET_SEND_SIZE',
  204. 'SOCKET_RECV_SIZE',
  205. 'CONNECT_TIMEOUT',
  206. 'RETRY_TIMEOUT',
  207. 'SEND_TIMEOUT',
  208. 'RECV_TIMEOUT',
  209. 'POLL_TIMEOUT',
  210. 'CACHE_LOOKUPS',
  211. 'SERVER_FAILURE_LIMIT');
  212. $memcached = new Memcached();
  213. $memcached->addServers($servers);
  214. foreach($options as $name => $value)
  215. {
  216. $name = strtoupper($name);
  217. if(!in_array($name, $memOptions) || !defined('Memcached::OPT_' . $name))
  218. continue;
  219. switch($name)
  220. {
  221. case 'HASH':
  222. $value = strtoupper($value);
  223. if(!defined('Memcached::HASH_' . $value))
  224. throw new StashMemcached_MemcachedError('Memcached option ' . $name . ' requires valid memcache hash option value');
  225. $value = constant('Memcached::HASH_' . $value);
  226. break;
  227. case 'DISTRIBUTION':
  228. $value = strtoupper($value);
  229. if(!defined('Memcached::DISTRIBUTION_' . $value))
  230. throw new StashMemcached_MemcachedError('Memcached option ' . $name . ' requires valid memcache distribution option value');
  231. $value = constant('Memcached::DISTRIBUTION_' . $value);
  232. break;
  233. case 'SERIALIZER':
  234. $value = strtoupper($value);
  235. if(!defined('Memcached::SERIALIZER_' . $value))
  236. throw new StashMemcached_MemcachedError('Memcached option ' . $name . ' requires valid memcache serializer option value');
  237. $value = constant('Memcached::SERIALIZER_' . $value);
  238. break;
  239. case 'SOCKET_SEND_SIZE':
  240. case 'SOCKET_RECV_SIZE':
  241. case 'CONNECT_TIMEOUT':
  242. case 'RETRY_TIMEOUT':
  243. case 'SEND_TIMEOUT':
  244. case 'RECV_TIMEOUT':
  245. case 'POLL_TIMEOUT':
  246. case 'SERVER_FAILURE_LIMIT':
  247. if(!is_numeric($value))
  248. throw new StashMemcached_MemcachedError('Memcached option ' . $name . ' requires numeric value');
  249. break;
  250. case 'PREFIX_KEY':
  251. if(!is_string($value))
  252. throw new StashMemcached_MemcachedError('Memcached option ' . $name . ' requires string value');
  253. break;
  254. case 'COMPRESSION':
  255. case 'LIBKETAMA_COMPATIBLE':
  256. case 'BUFFER_WRITES':
  257. case 'BINARY_PROTOCOL':
  258. case 'NO_BLOCK':
  259. case 'TCP_NODELAY':
  260. case 'CACHE_LOOKUPS':
  261. if(!is_bool($value))
  262. throw new StashMemcached_MemcachedError('Memcached option ' . $name . ' requires boolean value');
  263. break;
  264. }
  265. $memcached->setOption(constant('Memcached::OPT_' . $name), $value);
  266. }
  267. $this->memcached = $memcached;
  268. }
  269. public function set($key, $value, $expire = null)
  270. {
  271. return $this->memcached->set($key, array('data' => $value, 'expiration' => $expire), $expire);
  272. }
  273. public function get($key)
  274. {
  275. $value = $this->memcached->get($key);
  276. if($value === false && $this->memcached->getResultCode() == Memcached::RES_NOTFOUND)
  277. return false;
  278. return $value;
  279. }
  280. public function cas($key, $value)
  281. {
  282. if(($rValue = $this->memcached->get($key, null, $token)) !== false)
  283. return $rValue;
  284. if($this->memcached->getResultCode() === Memcached::RES_NOTFOUND)
  285. {
  286. $this->memcached->add($key, $value);
  287. }else{
  288. $this->memcached->cas($token, $key, $value);
  289. }
  290. return $value;
  291. }
  292. public function inc($key)
  293. {
  294. $this->cas($key, 0);
  295. return $this->memcached->increment($key);
  296. }
  297. public function flush()
  298. {
  299. $this->memcached->flush();
  300. }
  301. }
  302. class StashMemcached_Memcache extends StashMemcached_Memcached
  303. {
  304. public function initialize($servers, $options = array())
  305. {
  306. $memcached = new Memcache();
  307. foreach($servers as $server)
  308. {
  309. $host = $server[0];
  310. $port = isset($server[1]) ? $server[1] : 11211;
  311. $weight = isset($server[2]) ? $server[2] : null;
  312. if(is_numeric($weight))
  313. {
  314. $memcached->addServer($host, $port, true, $weight);
  315. }else{
  316. $memcached->addServer($host, $port);
  317. }
  318. }
  319. $this->memcached = $memcached;
  320. }
  321. public function set($key, $value, $expire = null)
  322. {
  323. return $this->memcached->set($key, array('data' => $value, 'expiration' => $expire), null, $expire);
  324. }
  325. public function get($key)
  326. {
  327. return @$this->memcached->get($key);
  328. }
  329. public function cas($key, $value)
  330. {
  331. if(($return = @$this->memcached->get($key)) !== false)
  332. return $return;
  333. $this->memcached->set($key, $value);
  334. return $value;
  335. }
  336. }
  337. class StashMemcachedError extends StashError {}
  338. class StashMemcached_MemcachedError extends StashError {}
  339. ?>