* @category Horde * @copyright 2013-2016 Horde LLC * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 * @package HashTable * * @property-read boolean $locking Does hash table provide locking? (@since * 1.1.0) * @property-read boolean $persistent Does hash table provide persistent * storage? * @property-write string $prefix Set the hash key prefix. */ abstract class Horde_HashTable_Base implements ArrayAccess, Serializable { /** * A list of items known not to exist. * * @var array */ protected $_noexist = array(); /** * Configuration parameters. * * @var array */ protected $_params = array( 'prefix' => 'hht_' ); /** * Persistent storage provided by driver? * * @var boolean */ protected $_persistent = false; /** * Constructor. * * @param array $params Configuration parameters: *
* - logger: (Horde_Log_Logger) Logger object.
* - prefix: (string) Prefix to use for key storage.
*
*
* @throws Horde_HashTable_Exception
*/
public function __construct(array $params = array())
{
$this->_params = array_merge($this->_params, $params);
$this->_init();
}
/**
* Do initialization.
*
* @throws Horde_HashTable_Exception
*/
protected function _init()
{
}
/**
*/
public function __set($name, $val)
{
switch ($name) {
case 'prefix':
$this->_params['prefix'] = $val;
break;
}
}
/**
*/
public function __get($name)
{
switch ($name) {
case 'locking':
return ($this instanceof Horde_HashTable_Lock);
case 'persistent':
return $this->_persistent;
}
}
/**
* Delete a key(s).
*
* @param mixed $keys The key or an array of keys to delete.
*
* @return boolean True on success.
*/
public function delete($keys)
{
if (!is_array($keys)) {
$keys = array($keys);
}
if ($todo = array_diff($keys, array_keys($this->_noexist))) {
$to_delete = array_fill_keys(array_map(array($this, 'hkey'), $todo), $todo);
if (!$this->_delete(array_keys($to_delete))) {
return false;
}
if (!empty($this->_params['logger'])) {
$this->_params['logger']->debug(sprintf(
'%s: Deleted keys (%s)',
get_class($this),
implode(',', array_keys($to_delete))
));
}
$this->_noexist = array_merge($this->_noexist, array_fill_keys(array_values($todo), true));
}
return true;
}
/**
* Delete keys.
*
* @param array $key An array of keys to delete.
*
* @return boolean True on success.
*/
abstract protected function _delete($keys);
/**
* Do the keys exists?
*
* @param mixed $keys The key or an array of keys.
*
* @return mixed A boolean/array of booleans indicating existence (return
* type is the type of $keys).
*/
public function exists($keys)
{
return $this->_getExists($keys, array($this, '_exists'));
}
/**
* Get data associated with keys.
*
* @param array $keys An array of keys.
*
* @return array Existence check. Values are boolean true/false.
*/
abstract protected function _exists($keys);
/**
* Get data associated with a key(s).
*
* @param mixed $keys The key or an array of keys.
*
* @return mixed The string/array on success (return type is the type of
* $keys); false value(s) on failure.
*/
public function get($keys)
{
return $this->_getExists($keys, array($this, '_get'));
}
/**
* Get data associated with keys.
*
* @param array $keys An array of keys.
*
* @return array The retrieved keys. Non-existent keys should return
* false as the value.
*/
abstract protected function _get($keys);
/**
* Does a get/exists action on a set of keys.
*
* @param mixed $keys The key or an array of keys.
* @param callable $callback The internal callback action.
*
* @return mixed The results.
*/
protected function _getExists($keys, $callback)
{
$noexist = $out = $todo = array();
if (!($ret_array = is_array($keys))) {
$keys = array($keys);
}
foreach ($keys as $val) {
if (isset($this->_noexist[$val])) {
$out[$val] = false;
$noexist[] = $val;
} else {
$todo[$this->hkey($val)] = $val;
}
}
if (!empty($todo)) {
foreach (call_user_func($callback, array_keys($todo)) as $key => $val) {
if ($val === false) {
$this->_noexist[$todo[$key]] = true;
$noexist[] = $todo[$key];
}
$out[$todo[$key]] = $val;
}
}
if (!empty($this->_params['logger'])) {
if ($tmp = array_diff(array_keys($out), $noexist)) {
$this->_params['logger']->debug(sprintf(
'%s: Retrieved keys (%s)',
get_class($this),
implode(',', $tmp)
));
}
if (!empty($noexist)) {
$this->_params['logger']->debug(sprintf(
'%s: Non-existent keys (%s)',
get_class($this),
implode(',', $noexist)
));
}
}
return $ret_array
? $out
: reset($out);
}
/**
* Set the value of a key.
*
* @param string $key The key.
* @param string $val The string to store.
* @param array $opts Additional options:
*
* - expire: (integer) Expiration time in seconds.
* DEFAULT: Doesn't expire.
* - replace: (boolean) Replace the value of key. If key doesn't exist,
* returns false.
* DEFAULT: false
*
*
* @return boolean True on success, false on error.
*/
public function set($key, $val, array $opts = array())
{
if (!empty($opts['replace']) && isset($this->_noexist[$key])) {
return false;
}
/* @todo BC: 'timeout' == 'expire' usage. */
if (isset($opts['timeout']) && !isset($opts['expire'])) {
$opts['expire'] = $opts['timeout'];
}
if ($this->_set($this->hkey($key), $val, $opts)) {
unset($this->_noexist[$key]);
$res = true;
} else {
$res = false;
}
if (!empty($this->_params['logger'])) {
$this->_params['logger']->debug(sprintf(
'%s: Set key %s(%s)',
get_class($this),
$res ? '' : 'FAILED ',
$key
));
}
return $res;
}
/**
* Set the value of a key.
*
* @param string $key The key.
* @param string $val The string to store.
* @param array $opts Additional options (see set()).
*
* @return boolean True on success.
*/
abstract protected function _set($key, $val, $opts);
/**
* Clear all hash table entries.
*/
abstract public function clear();
/**
* Add local prefix to beginning of key.
*
* @param string $key Key name.
*
* @return string Hash table key identifier.
*/
public function hkey($key)
{
return $this->_params['prefix'] . $key;
}
/* ArrayAccess methods. */
/**
*/
public function offsetExists($offset)
{
return $this->exists($offset);
}
/**
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
*/
public function offsetSet($offset, $value)
{
return $this->set($offset, $value);
}
/**
*/
public function offsetUnset($offset)
{
return $this->delete($offset);
}
/* Serializable methods. */
/**
*/
public function serialize()
{
return serialize($this->_params);
}
/**
*/
public function unserialize($data)
{
$this->_params = @unserialize($data);
$this->_init();
}
}