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