Files
server/usr/share/psa-pear/pear/php/Horde/HashTable/Predis.php
2026-01-07 20:52:11 +01:00

204 lines
4.5 KiB
PHP

<?php
/**
* Copyright 2013-2016 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @category Horde
* @copyright 2013-2016 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package HashTable
*/
/**
* Implementation of HashTable for a Redis server (using the Predis library).
*
* See: https://github.com/nrk/predis
*
* @author Michael Slusarz <slusarz@horde.org>
* @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:
* <pre>
* - predis: (Predis\Client) [REQUIRED] Predis client object.
* </pre>
*/
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]);
}
}