All pastes #725535 Raw Edit

mcd.php

public php v1 · immutable
#725535 ·published 2007-10-04 14:29 UTC
rendered paste body
<?php/** * memcached driver for PEAR::Cache using PEAR::Net_Socket * * This driver was made to provide a memcached connector without using * the pecl extension for PHP. You can use as many memcached servers as you want, * you just have to put them in the constructor options list like this: * * $options = array('servers' => array('server1:11211', 'server1:11211', 'server1:11211')); * * Timeout for dead server recheck can be set in $options['recheckTimeout']. * Default is 1 second. * Timeout for TCP connect can be set in $options['connectTimeout']. * Default is 0.05 seconds. * * PHP versions 4 and 5 * * LICENSE: This source file is subject to version 3.0 of the PHP license * that is available through the world-wide-web at the following URI: * http://www.php.net/license/3_0.txt.  If you did not receive a copy of * the PHP License and are unable to obtain it through the web, please * send a note to license@php.net so we can mail you a copy immediately. * * @category  Cache * @package   Cache * @author    Dieter Rothacker <dieter.rothacker@dmc.de> * @copyright 1997-2007 dmc digital media center GmbH * @license   http://www.php.net/license/3_0.txt  PHP License 3.0 * @version   CVS: $Id: $ * @link      http://pear.php.net/package/Cache * @see       Cache/Container.php * @since     File available since Release 1.5.6 */require_once 'Cache/Container.php';require_once 'Net/Socket.php';/** * memcached container driver class using PEAR::Net_Socket methods * * @category  Cache * @package   Cache * @author    Dieter Rothacker <dieter.rothacker@dmc.de> * @copyright 1997-2007 dmc digital media center GmbH * @license   http://www.php.net/license/3_0.txt  PHP License 3.0 * @version   1.0.0 * @link      http://pear.php.net/package/Cache */class Cache_Container_mcd extends Cache_Container{    /**     * the classes version     *     * @access private     * @var string $version     */    var $version = '1.0.0';    /**     * socket to establish connection     *     * @access private     * @var handle $socket     */    var $socket = null;    /**     * list of possible servers to connect to     *     * @access private     * @var array $servers     */    var $servers = array('localhost:11211');    /**     * currently connected server number corresponding     * to index-number in $servers     *     * @access private     * @var string $currentServer     */    var $currentServer = null;    /**     * blacklist for down servers     *     * @access private     * @var array $blacklist     */    var $blacklist = array();    /**     * timeout in seconds to recheck blacklisted servers     * default: 1 second     *     * @access private     * @var float $recheckTimeout     */    var $recheckTimeout = 1;    /**     * connect-timeout in seconds     * default: 50ms     *     * @access private     * @var float $connectTimeout     */    var $connectTimeout = 0.05;    /**     * Options that can be set. Overriden from parent.     * Lowwater + Highwater omitted on purpose because they have to be set     * when initializing the memcached daemon     *     * @access private     * @var array     */    var $allowedOptions = array('encoding_mode', 'servers', 'recheckTimeout', 'connectTimeout');    /**     * PHP4 constructor     *     * @param array $options array 'server' has to contain 'ip:port' elements     *     * @access public     * @return void     */    function Cache_Container_mcd($options)    {        /* register destructor */        register_shutdown_function(array(&$this, '__destruct'));        /* call real constructor */        $this->__construct($options);    }    /* end func Cache_Container_mcd */    /**     * constructor     *     * @param array $options array 'server' has to contain 'ip:port' elements     *     * @access public     * @return mixed $returnValue  null or Cache_Error     */    function __construct($options)    {        if (is_array($options)) {            $this->setOptions($options, $this->allowedOptions);        }        $this->socket = new Net_Socket();    }    /* end func __construct */    /**     * destructor     *     * @access public     * @return void     */    function __destruct()    {        $this->socket->disconnect();    }    /* end func __destruct */    /**     * generates new hashkey in case of down servers     *     * @param string $key old hashkey for cachedata     *     * @access private     * @return string  $key   new hashkey for cachedata     */    function getNewHashKey($key)    {        $returnValue = md5(serialize($key . 9));        return $returnValue;    }    /* end func getNewHashKey */    /**     * establishes connection to server.     *     * @param integer $requestedServer server number in $servers     *     * @access private     * @return mixed $returnValue  true if connection was established, else false     */    function establishConnection($requestedServer)    {        $returnValue = true;        $sResult     = null;        $parts       = array();        // if not already connected to requested server        if ($this->currentServer != $requestedServer) {            $this->currentServer = $requestedServer;            $parts               = explode(':', $this->servers[$requestedServer]);            $server              = $parts[0];            $port                = $parts[1];            $sResult             = $this->socket->connect($server, $port, false, $this->connectTimeout);        }        if (Net_Socket::isError($sResult)) {            $returnValue = false;        }        return $returnValue;    }    /* end func establishConnection */    /**     * checks if all servers are on blacklist (not connectable)     *     * @access private     * @return string $returnValue  true if all servers are down, else false     */    function areAllServersDown()    {        $returnValue = false;        if (count($this->blacklist) == count($this->servers)) {            $returnValue = true;        }        return $returnValue;    }    /* end func areAllServersDown */    /**     * returns new server number which is not blacklisted or returns false     *   - if all servers are on blacklist (all servers are not connectable) or     *   - if hashKey is not a md5 encoded string     *     * @param string $hashKey for cachedata     *     * @access private     * @return String $returnValue  serverNumber or false if all servers are on blacklist or hash is no md5     */    function getNewServerId($hashKey)    {        $returnValue     = false;        $decValue        = false;        $requestedServer = false;        if (strlen($hashKey) == 32) {            // compute new $requestedServer            $decValue        = substr($hashKey, 0, 7);            $requestedServer = $decValue % count($this->servers);            if ($this->areAllServersDown()) {                $returnValue = false;            } elseif ($this->isServerOnBlacklist($requestedServer)) {                // try to connect to another server if current got blacklisted                $returnValue = $this->getNewServerId($this->getNewHashKey($hashKey));            } else {                if ($this->isServerRunning($requestedServer)) {                    $returnValue = $requestedServer;                } else {                    // current server not reachable but not yet blacklisted                    $this->setServerToBlacklist($requestedServer);                    $returnValue = $this->getNewServerId($this->getNewHashKey($hashKey));                }            }        } else {            $returnValue = new Cache_Error('getNewServerId: Hashkey is no valid md5', __FILE__, __LINE__);        }        return $returnValue;    }    /* end func getNewServerId */    /**     * gets the version of the memacache server to check     * if server is running. if connection to server can't be     * established, server is set to blacklist.     *     * @param string $requestedServer number of server in order as in $server array     *     * @access private     * @return string $returnValue  if server is running true, else false     */    function isServerRunning($requestedServer)    {        $returnValue = false;        $isConnected = false;        $sResult     = false;        $version     = false;        if ($requestedServer !== false) {            $isConnected = $this->establishConnection($requestedServer);            if ($isConnected) {                $sResult = $this->socket->writeLine('version');                if (!Net_Socket::isError($sResult)) {                    $sResult = $this->socket->readLine();                    if (substr($sResult, 0, 7) == 'VERSION') {                        $returnValue = true;                    }                }            } else {                $this->setServerToBlacklist($requestedServer);            }        }        return $returnValue;    }    /* end func IsServerRunning */    /**     * Fetches a dataset from the storage medium.     *     * @param string $id    dataset ID     * @param string $group cache groups are not supported     *     * @return mixed $returnValue  array, format: [expire date, cached data, user data] or Cache_Error     * @access public     */    function fetch($id, $group)    {        $userData    = 0;        $data        = false;        $sResult     = false;        $returnValue = array();        $this->freeBlacklistedServers();        $serverToFetch = $this->getNewServerId($id);        if (!($serverToFetch === false)) {            $sResult = $this->socket->writeLine('get ' . $id);            if (Net_Socket::isError($sResult)) {                $returnValue = new Cache_Error('Command "get": error -> Check your parameters', __FILE__, __LINE__);            } else {                $sResult = $this->socket->readLine();                if ($sResult == 'END' || $sResult == 'ERROR') {                    $returnValue = new Cache_Error('Command "get": no such key available', __FILE__, __LINE__);                } else {                    $dataLine = explode(' ', $sResult);                    $userData = $dataLine[2];                    $sResult  = $this->socket->readLine();                    $data     = $this->decode($sResult);                    $returnValue = array($data[0],                        $data[1],                        $userData                        );                    $this->socket->readLine();                }            }        } else {            $returnValue = new Cache_Error('Command "get": all servers are unreachable', __FILE__, __LINE__);        }        return $returnValue;    }    /* end func fetch */    /**     * Checks if a dataset exists     *     * @param string $id    dataset ID     * @param string $group cache group (not supported)     *     * @return boolean $returnValue  true, if id exists else false     * @access public     */    function idExists($id, $group)    {        $returnValue = false;        $returnValue = $this->fetch($id, $group);        if (Net_Socket::isError($resultValue)) {            $returnValue = false;        } else {            $returnValue = true;        }        return $returnValue;    }    /* end func idExists */    /**     * removed cached data from server     *     * @param string $id    dataset ID     * @param string $group cache group     *     * @access private     * @return boolean $returnValue  true if removed     */    function remove($id, $group)    {        $returnValue = true;        $this->freeBlacklistedServers();        $serverToRemove = $this->getNewServerId($id);        if (!($serverToRemove === false)) {            $sResult = $this->socket->writeLine('delete ' . $id . ' 0');            $sResult = $this->socket->readLine();            if (Net_Socket::isError($sResult) || $sResult == 'NOT_FOUND') {                $returnValue = new Cache_Error('Could not remove data', __FILE__, __LINE__);            }        } else {            $returnValue = new Cache_Error('Could not remove data, all servers are down', __FILE__, __LINE__);        }        return $returnValue;    }    /* end func remove */    /**     * Returns the userdata field of a cached data set.     *     * @param string $id    dataset ID     * @param string $group cache group     *     * @return mixed User_Data Field or Cache_Error     * @access public     */    function getUserdata($id, $group)    {        $resultValue = $this->fetch($id, null);        if (!Net_Socket::isError($resultValue)) {            $resultValue = $resultValue[2];        }        return $resultValue;    }    /* end func getUserdata */    /**     * Stores a dataset     *     * @param string  $id       dataset ID     * @param mixed   $data     data to store     * @param integer $expire   expire date in seconds     * @param string  $group    cache groups are not supported     * @param string  $userdata userdefined data     *     * @return mixed $returnValue  Cache_Error or true if data was saved     * @access public     */    function save($id, $data, $expire, $group, $userdata)    {        $returnValue = false;        $sResult     = false;        $userdata    = (integer) $userdata;        $this->freeBlacklistedServers();        if (is_null($id)) {            $returnValue = new Cache_Error('Command "save": key error -> Key is needed to identify data', __FILE__, __LINE__);        }        if (!is_numeric($userdata) || ($userdata < 0) || ($userdata > pow(2, 16))) {            $returnValue = new Cache_Error('Command "save": userdata has to be an unsigned 16bit int', __FILE__, __LINE__);        }        if (!is_numeric($expire) || ($expire < 0)) {            $returnValue = new Cache_Error('Command "save": expire time has to be an unsigned int', __FILE__, __LINE__);        }        if (!is_a($returnValue, 'Cache_Error')) {            /* we put expiration time into cache data because            * memcache provides no functionality to read the            * expiration time of an already stored cache item */            $cacheData = $this->encode(array(                    time() + $expire,                    $data,));            $serverToSave = $this->getNewServerId($id);            if (!($serverToSave === false)) {                $sResult = $this->socket->writeLine('set ' . $id . ' ' . $userdata . ' ' . $expire . ' ' . strlen($cacheData));                if (Net_Socket::isError($sResult)) {                    $returnValue = new Cache_Error('Command "save": command error -> Check your parameters', __FILE__, __LINE__);                } else {                    $sResult = $this->socket->writeLine($cacheData);                    $sResult = $this->socket->readLine();                    if ($sResult == 'STORED') {                        $returnValue = true;                    }                }            } else {                $returnValue = new Cache_Error('Command "save": all servers are not reachable', __FILE__, __LINE__);            }        }        return $returnValue;    }    /* end func save */    /**     * Flushes the cache on all memcache servers     *     * @param string $group cache groups are not supported     *     * @return boolen $returnValue  true if data was flushed, else false     * @access public     */    function flush($group)    {        $returnValue = false;        $this->freeBlacklistedServers();        for ($i = 0; $i < count($this->servers); $i++) {            $result = $this->establishConnection($i);            if ($result === true) {                $sResult = $this->socket->writeLine('flush_all');                $sResult = $this->socket->readLine();                if (!Net_Socket::isError($sResult)) {                    $returnValue = true;                }            }        }        return $returnValue;    }    /* end func flush */    /**     * Checks if data is expired for future time.     * if it is expired, memcache daemon removes it from memory automatically     * (Hint: isExpired must be overriden, because memcache always returns actual time when     * pear cache expects the value 0 for "no expiration time")     *     * @param string $id      dataset ID     * @param string $group   cache groups are not supported     * @param string $max_age maximum cache item age     *     * @return boolean $returnValue  true if data is expired or not cached, else false     * @access public     */    function isExpired($id, $group, $max_age)    {        $returnValue = false;        $fetchResult = $this->fetch($id, $group);        if (!PEAR::isError($fetchResult)) {            $expireDate = $fetchResult[0];            if ((time() + $max_age) <= $expireDate) {                // is data expired in future point in time? (max_age)                $returnValue = false;            } elseif ($max_age === false) {                // this means data is cached, no future-check (max_age)                $returnValue = false;            } else {                // this means data is expired in future point in time (max_age)                $returnValue = true;            }        } else {            // data is expired because data couldn't be fetched            $returnValue = true;        }        return $returnValue;    }    /* end func isExpired */    /**     * Checks if a dataset is cached.     *     * @param string $id    dataset ID     * @param string $group cache groups are not supported     *     * @return boolean $returnValue  true if data is cached, else false     * @access public     */    function isCached($id, $group)    {        $returnValue = $this->fetch($id, $group);        if (Net_Socket::isError($returnValue)) {            $returnValue = false;        } else {            $returnValue = true;        }        return $returnValue;    }    /* end func isCached */    /**     * checks if $requestedServer is blacklisted     *     * @param integer $requestedServer server number     *     * @access private     * @return boolean $returnValue  true if server is blacklisted, else false     */    function isServerOnBlacklist($requestedServer)    {        $returnValue = array_key_exists($requestedServer, $this->blacklist);        return $returnValue;    }    /* end func isServerOnBlacklist */    /**     * sets $requestedServer to blacklist. checks before blacklisting     * if server is already on blacklist     *     * @param integer $requestedServer server number     *     * @access private     * @return boolean $returnValue  always true     */    function setServerToBlacklist($requestedServer)    {        $returnValue = true;        if (!$this->isServerOnBlacklist($requestedServer)) {            $this->blacklist[$requestedServer] = time();        }        return $returnValue;    }    /* end func setServerToBlacklist */    /**     * Removes servers from blacklist if time has passed     *     * @access private     * @return boolean $returnValue  always true     */    function freeBlacklistedServers()    {        $returnValue = true;        $serverNo    = false;        $timestamp   = false;        foreach ($this->blacklist as $serverNo => $timestamp) {            // free server if blacklisted for over $timeout            if ((time() - $timestamp) > $this->recheckTimeout) {                unset($this->blacklist[$serverNo]);            }        }        return $returnValue;    }    /* end func freeBlacklistedServers */}?>