PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/storage/cache/adapter/Memcache.php

http://github.com/UnionOfRAD/lithium
PHP | 329 lines | 110 code | 27 blank | 192 comment | 14 complexity | 0168b2c31adce4ae4e2ac7b897228cd7 MD5 | raw file
  1. <?php
  2. /**
  3. * li₃: the most RAD framework for PHP (http://li3.me)
  4. *
  5. * Copyright 2009, Union of RAD. All rights reserved. This source
  6. * code is distributed under the terms of the BSD 3-Clause License.
  7. * The full license text can be found in the LICENSE.txt file.
  8. */
  9. namespace lithium\storage\cache\adapter;
  10. use Memcached;
  11. use lithium\util\Set;
  12. use lithium\storage\Cache;
  13. use lithium\net\HostString;
  14. /**
  15. * Memcache (libmemcached) cache adapter implementation using `pecl/memcached`.
  16. *
  17. * This adapter requires `pecl/memcached` to be installed. The extension
  18. * must be enabled according to the extension documention and a running
  19. * memcached server instance must be available.
  20. *
  21. * This adapter natively handles multi-key reads/writes/deletes, natively
  22. * provides serialization and key scoping features and supports atomic
  23. * increment/decrement operations as well as clearing the entire cache.
  24. * Delegation of method calls to the connection object is available.
  25. *
  26. * Cached item persistence is not guaranteed. Infrequently used items will
  27. * be evicted from the cache when there is no room to store new ones.
  28. *
  29. * A simple configuration can be accomplished as follows:
  30. *
  31. * ```
  32. * Cache::config([
  33. * 'default' => [
  34. * 'adapter' => 'Memcached',
  35. * 'host' => '127.0.0.1:11211'
  36. * ]
  37. * ]);
  38. * ```
  39. *
  40. * The `'host'` key accepts entries in multiple formats, depending on the number of
  41. * Memcache servers you are connecting to. See the `__construct()` method for more
  42. * information.
  43. *
  44. * @link http://php.net/class.memcached.php
  45. * @link http://pecl.php.net/package/memcached
  46. * @see lithium\storage\cache\adapter\Memcache::__construct()
  47. * @see lithium\storage\Cache::key()
  48. * @see lithium\storage\Cache::adapter()
  49. */
  50. class Memcache extends \lithium\storage\cache\Adapter {
  51. /**
  52. * The default host used to connect to the server.
  53. */
  54. const DEFAULT_HOST = '127.0.0.1';
  55. /**
  56. * The default port used to connect to the server.
  57. */
  58. const DEFAULT_PORT = 11211;
  59. /**
  60. * `Memcached` object instance used by this adapter.
  61. *
  62. * @var object
  63. */
  64. public $connection = null;
  65. /**
  66. * Constructor. Instantiates the `Memcached` object, adds appropriate servers to the pool,
  67. * and configures any optional settings passed (see the `_init()` method).
  68. *
  69. * @see lithium\storage\Cache::config()
  70. * @param array $config Configuration for this cache adapter. These settings are queryable
  71. * through `Cache::config('name')`. The available options are as follows:
  72. * - `'scope'` _string_: Scope which will prefix keys; per default not set.
  73. * - `'expiry'` _mixed_: The default expiration time for cache values, if no value
  74. * is otherwise set. Can be either a `strtotime()` compatible tring or TTL in
  75. * seconds. To indicate items should not expire use `Cache::PERSIST`. Defaults
  76. * to `+1 hour`.
  77. * - `'host'` _string|array_: A string in the form of `'<host>'`, `'<host>:<port>'` or
  78. * `':<port>'` indicating the host and/or port to connect to. When one or both are
  79. * not provided uses general server defaults.
  80. * Use the array format for multiple hosts (optionally with server selection weights):
  81. * `array('167.221.1.5:11222', '167.221.1.6')`
  82. * `array('167.221.1.5:11222' => 200, '167.221.1.6')`
  83. * @return void
  84. */
  85. public function __construct(array $config = []) {
  86. $defaults = [
  87. 'scope' => null,
  88. 'expiry' => '+1 hour',
  89. 'host' => static::DEFAULT_HOST . ':' . static::DEFAULT_PORT
  90. ];
  91. parent::__construct(Set::merge($defaults, $config));
  92. }
  93. /**
  94. * Generates safe cache keys.
  95. *
  96. * As per the protocol no control characters or whitespace is allowed
  97. * in the key name. There's also a limit of max. 250 characters which is
  98. * checked and enforced here. The limit is actually lowered to 250 minus
  99. * the length of an crc32b hash minus separator (241) minus scope length
  100. * minus separator (241 - x).
  101. *
  102. * @param array $keys The original keys.
  103. * @return array Keys modified and safe to use with adapter.
  104. */
  105. public function key(array $keys) {
  106. $length = 241 - ($this->_config['scope'] ? strlen($this->_config['scope']) + 1 : 0);
  107. return array_map(
  108. function($key) use ($length) {
  109. $result = substr(preg_replace('/[[:cntrl:]\s]/u', '_', $key), 0, $length);
  110. return $key !== $result ? $result . '_' . hash('crc32b', $key) : $result;
  111. },
  112. $keys
  113. );
  114. }
  115. /**
  116. * Handles the actual `Memcached` connection and server connection
  117. * adding for the adapter constructor and sets prefix using the scope
  118. * if provided.
  119. *
  120. * @return void
  121. */
  122. protected function _init() {
  123. $this->connection = $this->connection ?: new Memcached();
  124. $this->connection->addServers($this->_formatHostList($this->_config['host']));
  125. if ($this->_config['scope']) {
  126. $this->connection->setOption(Memcached::OPT_PREFIX_KEY, "{$this->_config['scope']}:");
  127. }
  128. }
  129. /**
  130. * Dispatches a not-found method to the connection object. That way, one can
  131. * easily use a custom method on the adapter. If you want to know, what methods
  132. * are available, have a look at the documentation of memcached.
  133. *
  134. * ```
  135. * Cache::adapter('memcache')->methodName($argument);
  136. * ```
  137. *
  138. * @link http://php.net/class.memcached.php
  139. * @param string $method Name of the method to call.
  140. * @param array $params Parameter list to use when calling $method.
  141. * @return mixed Returns the result of the method call.
  142. */
  143. public function __call($method, $params = []) {
  144. return call_user_func_array([&$this->connection, $method], $params);
  145. }
  146. /**
  147. * Determines if a given method can be called.
  148. *
  149. * @deprecated
  150. * @param string $method Name of the method.
  151. * @param boolean $internal Provide `true` to perform check from inside the
  152. * class/object. When `false` checks also for public visibility;
  153. * defaults to `false`.
  154. * @return boolean Returns `true` if the method can be called, `false` otherwise.
  155. */
  156. public function respondsTo($method, $internal = false) {
  157. $message = '`' . __METHOD__ . '()` has been deprecated. ';
  158. $message .= 'Use `is_callable([$adapter->connection, \'<method>\'])` instead.';
  159. trigger_error($message, E_USER_DEPRECATED);
  160. if (parent::respondsTo($method, $internal)) {
  161. return true;
  162. }
  163. return is_callable([$this->connection, $method]);
  164. }
  165. /**
  166. * Formats standard `'host:port'` strings into arrays used by `Memcached`.
  167. *
  168. * @param mixed $host A host string in `'host:port'` format, or an array of host strings
  169. * optionally paired with relative selection weight values.
  170. * @return array Returns an array of `Memcached` server definitions.
  171. */
  172. protected function _formatHostList($host) {
  173. $hosts = [];
  174. foreach ((array) $this->_config['host'] as $host => $weight) {
  175. $host = HostString::parse(($hasWeight = is_integer($weight)) ? $host : $weight) + [
  176. 'host' => static::DEFAULT_HOST,
  177. 'port' => static::DEFAULT_PORT
  178. ];
  179. $host = [$host['host'], $host['port']];
  180. if ($hasWeight) {
  181. $host[] = $weight;
  182. }
  183. $hosts[] = $host;
  184. }
  185. return $hosts;
  186. }
  187. /**
  188. * Write values to the cache. All items to be cached will receive an
  189. * expiration time of `$expiry`.
  190. *
  191. * Expiration is always based off the current unix time in order to gurantee we never
  192. * exceed the TTL limit of 30 days when specifying the TTL directly.
  193. *
  194. * @param array $keys Key/value pairs with keys to uniquely identify the to-be-cached item.
  195. * @param string|integer $expiry A `strtotime()` compatible cache time or TTL in seconds.
  196. * To persist an item use `\lithium\storage\Cache::PERSIST`.
  197. * @return boolean `true` on successful write, `false` otherwise.
  198. */
  199. public function write(array $keys, $expiry = null) {
  200. $expiry = $expiry || $expiry === Cache::PERSIST ? $expiry : $this->_config['expiry'];
  201. if (!$expiry || $expiry === Cache::PERSIST) {
  202. $expires = 0;
  203. } elseif (is_int($expiry)) {
  204. $expires = $expiry + time();
  205. } else {
  206. $expires = strtotime($expiry);
  207. }
  208. if (count($keys) > 1) {
  209. return $this->connection->setMulti($keys, $expires);
  210. }
  211. return $this->connection->set(key($keys), current($keys), $expires);
  212. }
  213. /**
  214. * Read values from the cache. Will attempt to return an array of data
  215. * containing key/value pairs of the requested data.
  216. *
  217. * @param array $keys Keys to uniquely identify the cached items.
  218. * @return array Cached values keyed by cache keys on successful read,
  219. * keys which could not be read will not be included in
  220. * the results array.
  221. */
  222. public function read(array $keys) {
  223. if (count($keys) > 1) {
  224. if (!$results = $this->connection->getMulti($keys)) {
  225. return [];
  226. }
  227. } else {
  228. $result = $this->connection->get($key = current($keys));
  229. if ($result === false && $this->connection->getResultCode() === Memcached::RES_NOTFOUND) {
  230. return [];
  231. }
  232. $results = [$key => $result];
  233. }
  234. return $results;
  235. }
  236. /**
  237. * Will attempt to remove specified keys from the user space cache.
  238. *
  239. * @param array $keys Keys to uniquely identify the cached items.
  240. * @return boolean `true` on successful delete, `false` otherwise.
  241. */
  242. public function delete(array $keys) {
  243. if (count($keys) > 1) {
  244. return $this->connection->deleteMulti($keys);
  245. }
  246. return $this->connection->delete(current($keys));
  247. }
  248. /**
  249. * Performs an atomic decrement operation on specified numeric cache item.
  250. *
  251. * Note that, as per the Memcached specification:
  252. * "If the item's value is not numeric, it is treated as if the value were 0.
  253. * If the operation would decrease the value below 0, the new value will be 0."
  254. *
  255. * @link http://php.net/manual/memcached.decrement.php
  256. * @param string $key Key of numeric cache item to decrement.
  257. * @param integer $offset Offset to decrement - defaults to `1`.
  258. * @return integer|boolean The item's new value on successful decrement, else `false`.
  259. */
  260. public function decrement($key, $offset = 1) {
  261. return $this->connection->decrement($key, $offset);
  262. }
  263. /**
  264. * Performs an atomic increment operation on specified numeric cache item.
  265. *
  266. * Note that, as per the Memcached specification:
  267. * "If the item's value is not numeric, it is treated as if the value were 0."
  268. *
  269. * @link http://php.net/manual/memcached.decrement.php
  270. * @param string $key Key of numeric cache item to increment.
  271. * @param integer $offset Offset to increment - defaults to `1`.
  272. * @return integer|boolean The item's new value on successful increment, else `false`.
  273. */
  274. public function increment($key, $offset = 1) {
  275. return $this->connection->increment($key, $offset);
  276. }
  277. /**
  278. * Clears entire cache by flushing it. All cache keys using the
  279. * configuration but *without* honoring the scope are removed.
  280. *
  281. * Internally keys are not removed but invalidated. Thus this
  282. * operation doesn't actually free memory on the instance.
  283. *
  284. * The behavior and result when removing a single key
  285. * during this process fails is unknown.
  286. *
  287. * @return boolean `true` on successful clearing, `false` otherwise.
  288. */
  289. public function clear() {
  290. return $this->connection->flush();
  291. }
  292. /**
  293. * Determines if the `Memcached` extension has been installed.
  294. *
  295. * @return boolean Returns `true` if the `Memcached` extension is installed and enabled, `false`
  296. * otherwise.
  297. */
  298. public static function enabled() {
  299. return extension_loaded('memcached');
  300. }
  301. }
  302. ?>