You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
329 lines
11 KiB
329 lines
11 KiB
<?php
|
|
/**
|
|
* li₃: the most RAD framework for PHP (http://li3.me)
|
|
*
|
|
* Copyright 2009, Union of RAD. All rights reserved. This source
|
|
* code is distributed under the terms of the BSD 3-Clause License.
|
|
* The full license text can be found in the LICENSE.txt file.
|
|
*/
|
|
|
|
namespace lithium\storage\cache\adapter;
|
|
|
|
use Memcached;
|
|
use lithium\util\Set;
|
|
use lithium\storage\Cache;
|
|
use lithium\net\HostString;
|
|
|
|
/**
|
|
* Memcache (libmemcached) cache adapter implementation using `pecl/memcached`.
|
|
*
|
|
* This adapter requires `pecl/memcached` to be installed. The extension
|
|
* must be enabled according to the extension documention and a running
|
|
* memcached server instance must be available.
|
|
*
|
|
* This adapter natively handles multi-key reads/writes/deletes, natively
|
|
* provides serialization and key scoping features and supports atomic
|
|
* increment/decrement operations as well as clearing the entire cache.
|
|
* Delegation of method calls to the connection object is available.
|
|
*
|
|
* Cached item persistence is not guaranteed. Infrequently used items will
|
|
* be evicted from the cache when there is no room to store new ones.
|
|
*
|
|
* A simple configuration can be accomplished as follows:
|
|
*
|
|
* ```
|
|
* Cache::config([
|
|
* 'default' => [
|
|
* 'adapter' => 'Memcached',
|
|
* 'host' => '127.0.0.1:11211'
|
|
* ]
|
|
* ]);
|
|
* ```
|
|
*
|
|
* The `'host'` key accepts entries in multiple formats, depending on the number of
|
|
* Memcache servers you are connecting to. See the `__construct()` method for more
|
|
* information.
|
|
*
|
|
* @link http://php.net/class.memcached.php
|
|
* @link http://pecl.php.net/package/memcached
|
|
* @see lithium\storage\cache\adapter\Memcache::__construct()
|
|
* @see lithium\storage\Cache::key()
|
|
* @see lithium\storage\Cache::adapter()
|
|
*/
|
|
class Memcache extends \lithium\storage\cache\Adapter {
|
|
|
|
/**
|
|
* The default host used to connect to the server.
|
|
*/
|
|
const DEFAULT_HOST = '127.0.0.1';
|
|
|
|
/**
|
|
* The default port used to connect to the server.
|
|
*/
|
|
const DEFAULT_PORT = 11211;
|
|
|
|
/**
|
|
* `Memcached` object instance used by this adapter.
|
|
*
|
|
* @var object
|
|
*/
|
|
public $connection = null;
|
|
|
|
/**
|
|
* Constructor. Instantiates the `Memcached` object, adds appropriate servers to the pool,
|
|
* and configures any optional settings passed (see the `_init()` method).
|
|
*
|
|
* @see lithium\storage\Cache::config()
|
|
* @param array $config Configuration for this cache adapter. These settings are queryable
|
|
* through `Cache::config('name')`. The available options are as follows:
|
|
* - `'scope'` _string_: Scope which will prefix keys; per default not set.
|
|
* - `'expiry'` _mixed_: The default expiration time for cache values, if no value
|
|
* is otherwise set. Can be either a `strtotime()` compatible tring or TTL in
|
|
* seconds. To indicate items should not expire use `Cache::PERSIST`. Defaults
|
|
* to `+1 hour`.
|
|
* - `'host'` _string|array_: A string in the form of `'<host>'`, `'<host>:<port>'` or
|
|
* `':<port>'` indicating the host and/or port to connect to. When one or both are
|
|
* not provided uses general server defaults.
|
|
* Use the array format for multiple hosts (optionally with server selection weights):
|
|
* `array('167.221.1.5:11222', '167.221.1.6')`
|
|
* `array('167.221.1.5:11222' => 200, '167.221.1.6')`
|
|
* @return void
|
|
*/
|
|
public function __construct(array $config = []) {
|
|
$defaults = [
|
|
'scope' => null,
|
|
'expiry' => '+1 hour',
|
|
'host' => static::DEFAULT_HOST . ':' . static::DEFAULT_PORT
|
|
];
|
|
parent::__construct(Set::merge($defaults, $config));
|
|
}
|
|
|
|
/**
|
|
* Generates safe cache keys.
|
|
*
|
|
* As per the protocol no control characters or whitespace is allowed
|
|
* in the key name. There's also a limit of max. 250 characters which is
|
|
* checked and enforced here. The limit is actually lowered to 250 minus
|
|
* the length of an crc32b hash minus separator (241) minus scope length
|
|
* minus separator (241 - x).
|
|
*
|
|
* @param array $keys The original keys.
|
|
* @return array Keys modified and safe to use with adapter.
|
|
*/
|
|
public function key(array $keys) {
|
|
$length = 241 - ($this->_config['scope'] ? strlen($this->_config['scope']) + 1 : 0);
|
|
|
|
return array_map(
|
|
function($key) use ($length) {
|
|
$result = substr(preg_replace('/[[:cntrl:]\s]/u', '_', $key), 0, $length);
|
|
return $key !== $result ? $result . '_' . hash('crc32b', $key) : $result;
|
|
},
|
|
$keys
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handles the actual `Memcached` connection and server connection
|
|
* adding for the adapter constructor and sets prefix using the scope
|
|
* if provided.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function _init() {
|
|
$this->connection = $this->connection ?: new Memcached();
|
|
$this->connection->addServers($this->_formatHostList($this->_config['host']));
|
|
|
|
if ($this->_config['scope']) {
|
|
$this->connection->setOption(Memcached::OPT_PREFIX_KEY, "{$this->_config['scope']}:");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatches a not-found method to the connection object. That way, one can
|
|
* easily use a custom method on the adapter. If you want to know, what methods
|
|
* are available, have a look at the documentation of memcached.
|
|
*
|
|
* ```
|
|
* Cache::adapter('memcache')->methodName($argument);
|
|
* ```
|
|
*
|
|
* @link http://php.net/class.memcached.php
|
|
* @param string $method Name of the method to call.
|
|
* @param array $params Parameter list to use when calling $method.
|
|
* @return mixed Returns the result of the method call.
|
|
*/
|
|
public function __call($method, $params = []) {
|
|
return call_user_func_array([&$this->connection, $method], $params);
|
|
}
|
|
|
|
/**
|
|
* Determines if a given method can be called.
|
|
*
|
|
* @deprecated
|
|
* @param string $method Name of the method.
|
|
* @param boolean $internal Provide `true` to perform check from inside the
|
|
* class/object. When `false` checks also for public visibility;
|
|
* defaults to `false`.
|
|
* @return boolean Returns `true` if the method can be called, `false` otherwise.
|
|
*/
|
|
public function respondsTo($method, $internal = false) {
|
|
$message = '`' . __METHOD__ . '()` has been deprecated. ';
|
|
$message .= 'Use `is_callable([$adapter->connection, \'<method>\'])` instead.';
|
|
trigger_error($message, E_USER_DEPRECATED);
|
|
|
|
if (parent::respondsTo($method, $internal)) {
|
|
return true;
|
|
}
|
|
return is_callable([$this->connection, $method]);
|
|
}
|
|
|
|
/**
|
|
* Formats standard `'host:port'` strings into arrays used by `Memcached`.
|
|
*
|
|
* @param mixed $host A host string in `'host:port'` format, or an array of host strings
|
|
* optionally paired with relative selection weight values.
|
|
* @return array Returns an array of `Memcached` server definitions.
|
|
*/
|
|
protected function _formatHostList($host) {
|
|
$hosts = [];
|
|
|
|
foreach ((array) $this->_config['host'] as $host => $weight) {
|
|
$host = HostString::parse(($hasWeight = is_integer($weight)) ? $host : $weight) + [
|
|
'host' => static::DEFAULT_HOST,
|
|
'port' => static::DEFAULT_PORT
|
|
];
|
|
$host = [$host['host'], $host['port']];
|
|
|
|
if ($hasWeight) {
|
|
$host[] = $weight;
|
|
}
|
|
$hosts[] = $host;
|
|
}
|
|
return $hosts;
|
|
}
|
|
|
|
/**
|
|
* Write values to the cache. All items to be cached will receive an
|
|
* expiration time of `$expiry`.
|
|
*
|
|
* Expiration is always based off the current unix time in order to gurantee we never
|
|
* exceed the TTL limit of 30 days when specifying the TTL directly.
|
|
*
|
|
* @param array $keys Key/value pairs with keys to uniquely identify the to-be-cached item.
|
|
* @param string|integer $expiry A `strtotime()` compatible cache time or TTL in seconds.
|
|
* To persist an item use `\lithium\storage\Cache::PERSIST`.
|
|
* @return boolean `true` on successful write, `false` otherwise.
|
|
*/
|
|
public function write(array $keys, $expiry = null) {
|
|
$expiry = $expiry || $expiry === Cache::PERSIST ? $expiry : $this->_config['expiry'];
|
|
|
|
if (!$expiry || $expiry === Cache::PERSIST) {
|
|
$expires = 0;
|
|
} elseif (is_int($expiry)) {
|
|
$expires = $expiry + time();
|
|
} else {
|
|
$expires = strtotime($expiry);
|
|
}
|
|
if (count($keys) > 1) {
|
|
return $this->connection->setMulti($keys, $expires);
|
|
}
|
|
return $this->connection->set(key($keys), current($keys), $expires);
|
|
}
|
|
|
|
/**
|
|
* Read values from the cache. Will attempt to return an array of data
|
|
* containing key/value pairs of the requested data.
|
|
*
|
|
* @param array $keys Keys to uniquely identify the cached items.
|
|
* @return array Cached values keyed by cache keys on successful read,
|
|
* keys which could not be read will not be included in
|
|
* the results array.
|
|
*/
|
|
public function read(array $keys) {
|
|
if (count($keys) > 1) {
|
|
if (!$results = $this->connection->getMulti($keys)) {
|
|
return [];
|
|
}
|
|
} else {
|
|
$result = $this->connection->get($key = current($keys));
|
|
|
|
if ($result === false && $this->connection->getResultCode() === Memcached::RES_NOTFOUND) {
|
|
return [];
|
|
}
|
|
$results = [$key => $result];
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Will attempt to remove specified keys from the user space cache.
|
|
*
|
|
* @param array $keys Keys to uniquely identify the cached items.
|
|
* @return boolean `true` on successful delete, `false` otherwise.
|
|
*/
|
|
public function delete(array $keys) {
|
|
if (count($keys) > 1) {
|
|
return $this->connection->deleteMulti($keys);
|
|
}
|
|
return $this->connection->delete(current($keys));
|
|
}
|
|
|
|
/**
|
|
* Performs an atomic decrement operation on specified numeric cache item.
|
|
*
|
|
* Note that, as per the Memcached specification:
|
|
* "If the item's value is not numeric, it is treated as if the value were 0.
|
|
* If the operation would decrease the value below 0, the new value will be 0."
|
|
*
|
|
* @link http://php.net/manual/memcached.decrement.php
|
|
* @param string $key Key of numeric cache item to decrement.
|
|
* @param integer $offset Offset to decrement - defaults to `1`.
|
|
* @return integer|boolean The item's new value on successful decrement, else `false`.
|
|
*/
|
|
public function decrement($key, $offset = 1) {
|
|
return $this->connection->decrement($key, $offset);
|
|
}
|
|
|
|
/**
|
|
* Performs an atomic increment operation on specified numeric cache item.
|
|
*
|
|
* Note that, as per the Memcached specification:
|
|
* "If the item's value is not numeric, it is treated as if the value were 0."
|
|
*
|
|
* @link http://php.net/manual/memcached.decrement.php
|
|
* @param string $key Key of numeric cache item to increment.
|
|
* @param integer $offset Offset to increment - defaults to `1`.
|
|
* @return integer|boolean The item's new value on successful increment, else `false`.
|
|
*/
|
|
public function increment($key, $offset = 1) {
|
|
return $this->connection->increment($key, $offset);
|
|
}
|
|
|
|
/**
|
|
* Clears entire cache by flushing it. All cache keys using the
|
|
* configuration but *without* honoring the scope are removed.
|
|
*
|
|
* Internally keys are not removed but invalidated. Thus this
|
|
* operation doesn't actually free memory on the instance.
|
|
*
|
|
* The behavior and result when removing a single key
|
|
* during this process fails is unknown.
|
|
*
|
|
* @return boolean `true` on successful clearing, `false` otherwise.
|
|
*/
|
|
public function clear() {
|
|
return $this->connection->flush();
|
|
}
|
|
|
|
/**
|
|
* Determines if the `Memcached` extension has been installed.
|
|
*
|
|
* @return boolean Returns `true` if the `Memcached` extension is installed and enabled, `false`
|
|
* otherwise.
|
|
*/
|
|
public static function enabled() {
|
|
return extension_loaded('memcached');
|
|
}
|
|
}
|
|
|
|
?>
|