* @category Horde * @copyright 2013-2016 Horde LLC * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 * @package HashTable */ class Horde_HashTable_Predis extends Horde_HashTable_Base implements Horde_HashTable_Lock { /* Suffix added to key to create the lock entry. */ const LOCK_SUFFIX = '_l'; /* Lock timeout (in seconds). */ const LOCK_TIMEOUT = 30; /** * Locked keys. * * @var array */ protected $_locks = array(); /** * Predis client object. * * @var Predis\Client */ protected $_predis; /** * @param array $params Additional configuration parameters: *
* - predis: (Predis\Client) [REQUIRED] Predis client object.
*
*/
public function __construct(array $params = array())
{
if (!isset($params['predis'])) {
throw InvalidArgumentException('Missing predis parameter.');
}
parent::__construct($params);
register_shutdown_function(array($this, 'shutdown'));
}
/**
*/
protected function _init()
{
$this->_predis = $this->_params['predis'];
}
/**
* Shutdown function.
*/
public function shutdown()
{
foreach (array_keys($this->_locks) as $key) {
$this->unlock($key);
}
}
/**
*/
protected function _delete($keys)
{
return (count($keys) == $this->_predis->del($keys));
}
/**
*/
protected function _exists($keys)
{
$pipeline = $this->_predis->pipeline();
foreach ($keys as $val) {
$pipeline->exists($val);
}
$exists = array();
foreach ($pipeline->execute() as $key => $val) {
$exists[$keys[$key]] = (bool)$val;
}
return $exists;
}
/**
*/
protected function _get($keys)
{
$keys = array_values($keys);
$out = array();
try {
$data = $this->_predis->mget($keys);
} catch (Exception $e) {
/* MGET doesn't work on clusters. */
$data = array();
foreach ($keys as $key) {
$data[$key] = $this->_predis->get($key);
}
}
foreach ($data as $key => $val) {
$out[$keys[$key]] = is_null($val)
? false
: $val;
}
return $out;
}
/**
*/
protected function _set($key, $val, $opts)
{
if (!empty($opts['replace']) && !$this->_predis->exists($key)) {
return false;
}
/* Can't use SETEX, since 2.0 server is not guaranteed. */
if (!$this->_predis->set($key, $val)) {
return false;
}
if (!empty($opts['expire'])) {
$this->_predis->expire($key, $opts['expire']);
}
return true;
}
/**
*/
public function clear()
{
try {
$res = $this->_predis->keys(addcslashes(strval($this->_params['prefix']), '?*') . '*');
/* Before 2.0, KEYS returns a space-delimited string. */
if (is_string($res)) {
$res = explode(' ', $res);
}
if ($res) {
$this->_predis->del($res);
}
} catch (Exception $e) {
}
}
/**
*/
public function hkey($key)
{
/* Key is MD5 encoded. But don't MD5 encode the prefix part, or else
* clear() won't work properly. */
return $this->_params['prefix'] . hash('md5', $key);
}
/**
*/
public function lock($key)
{
$hkey = $this->hkey($key) . self::LOCK_SUFFIX;
$i = 0;
while (!$this->_predis->setnx($hkey, 1)) {
usleep(min(pow(2, $i++) * 10000, 100000));
}
$this->_predis->expire($hkey, self::LOCK_TIMEOUT);
$this->_locks[$key] = true;
}
/**
*/
public function unlock($key)
{
$this->_predis->del($this->hkey($key) . self::LOCK_SUFFIX);
unset($this->_locks[$key]);
}
}