|
<?php |
|
// |
|
// +---------------------------------------------------------------------------+ |
|
// | memcached client, PHP | |
|
// +---------------------------------------------------------------------------+ |
|
// | Copyright (c) 2003 Ryan T. Dean <rtdean@cytherianage.net> | |
|
// | All rights reserved. | |
|
// | | |
|
// | Redistribution and use in source and binary forms, with or without | |
|
// | modification, are permitted provided that the following conditions | |
|
// | are met: | |
|
// | | |
|
// | 1. Redistributions of source code must retain the above copyright | |
|
// | notice, this list of conditions and the following disclaimer. | |
|
// | 2. Redistributions in binary form must reproduce the above copyright | |
|
// | notice, this list of conditions and the following disclaimer in the | |
|
// | documentation and/or other materials provided with the distribution. | |
|
// | | |
|
// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
|
// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
|
// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
|
// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
|
// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
|
// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
|
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
|
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
|
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
|
// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
|
// +---------------------------------------------------------------------------+ |
|
// | Author: Ryan T. Dean <rtdean@cytherianage.net> | |
|
// | Heavily influenced by the Perl memcached client by Brad Fitzpatrick. | |
|
// | Permission granted by Brad Fitzpatrick for relicense of ported Perl | |
|
// | client logic under 2-clause BSD license. | |
|
// +---------------------------------------------------------------------------+ |
|
// |
|
// $TCAnet$ |
|
// |
|
|
|
/** |
|
* This is the PHP client for memcached - a distributed memory cache daemon. |
|
* More information is available at http://www.danga.com/memcached/ |
|
* |
|
* Usage example: |
|
* |
|
* require_once 'memcached.php'; |
|
* |
|
* $mc = new memcached(array( |
|
* 'servers' => array('127.0.0.1:10000', |
|
* array('192.0.0.1:10010', 2), |
|
* '127.0.0.1:10020'), |
|
* 'debug' => false, |
|
* 'compress_threshold' => 10240, |
|
* 'persistant' => true)); |
|
* |
|
* $mc->add('key', array('some', 'array')); |
|
* $mc->replace('key', 'some random string'); |
|
* $val = $mc->get('key'); |
|
* |
|
* @author Ryan T. Dean <rtdean@cytherianage.net> |
|
* @version 0.1.2 |
|
*/ |
|
|
|
// {{{ requirements |
|
// }}} |
|
|
|
// {{{ class memcached |
|
/** |
|
* memcached client class implemented using (p)fsockopen() |
|
* |
|
* @author Ryan T. Dean <rtdean@cytherianage.net> |
|
* @ingroup Cache |
|
*/ |
|
class memcached |
|
{ |
|
// {{{ properties |
|
// {{{ public |
|
|
|
// {{{ constants |
|
// {{{ flags |
|
|
|
/** |
|
* Flag: indicates data is serialized |
|
*/ |
|
const SERIALIZED = 1; |
|
|
|
/** |
|
* Flag: indicates data is compressed |
|
*/ |
|
const COMPRESSED = 2; |
|
|
|
// }}} |
|
|
|
/** |
|
* Minimum savings to store data compressed |
|
*/ |
|
const COMPRESSION_SAVINGS = 0.20; |
|
|
|
// }}} |
|
|
|
|
|
/** |
|
* Command statistics |
|
* |
|
* @var array |
|
* @access public |
|
*/ |
|
var $stats; |
|
|
|
// }}} |
|
// {{{ private |
|
|
|
/** |
|
* Cached Sockets that are connected |
|
* |
|
* @var array |
|
* @access private |
|
*/ |
|
var $_cache_sock; |
|
|
|
/** |
|
* Current debug status; 0 - none to 9 - profiling |
|
* |
|
* @var boolean |
|
* @access private |
|
*/ |
|
var $_debug; |
|
|
|
/** |
|
* Dead hosts, assoc array, 'host'=>'unixtime when ok to check again' |
|
* |
|
* @var array |
|
* @access private |
|
*/ |
|
var $_host_dead; |
|
|
|
/** |
|
* Is compression available? |
|
* |
|
* @var boolean |
|
* @access private |
|
*/ |
|
var $_have_zlib; |
|
|
|
/** |
|
* Do we want to use compression? |
|
* |
|
* @var boolean |
|
* @access private |
|
*/ |
|
var $_compress_enable; |
|
|
|
/** |
|
* At how many bytes should we compress? |
|
* |
|
* @var integer |
|
* @access private |
|
*/ |
|
var $_compress_threshold; |
|
|
|
/** |
|
* Are we using persistant links? |
|
* |
|
* @var boolean |
|
* @access private |
|
*/ |
|
var $_persistant; |
|
|
|
/** |
|
* If only using one server; contains ip:port to connect to |
|
* |
|
* @var string |
|
* @access private |
|
*/ |
|
var $_single_sock; |
|
|
|
/** |
|
* Array containing ip:port or array(ip:port, weight) |
|
* |
|
* @var array |
|
* @access private |
|
*/ |
|
var $_servers; |
|
|
|
/** |
|
* Our bit buckets |
|
* |
|
* @var array |
|
* @access private |
|
*/ |
|
var $_buckets; |
|
|
|
/** |
|
* Total # of bit buckets we have |
|
* |
|
* @var integer |
|
* @access private |
|
*/ |
|
var $_bucketcount; |
|
|
|
/** |
|
* # of total servers we have |
|
* |
|
* @var integer |
|
* @access private |
|
*/ |
|
var $_active; |
|
|
|
/** |
|
* Stream timeout in seconds. Applies for example to fread() |
|
* |
|
* @var integer |
|
* @access private |
|
*/ |
|
var $_timeout_seconds; |
|
|
|
/** |
|
* Stream timeout in microseconds |
|
* |
|
* @var integer |
|
* @access private |
|
*/ |
|
var $_timeout_microseconds; |
|
|
|
/** |
|
* Connect timeout in seconds |
|
*/ |
|
var $_connect_timeout; |
|
|
|
/** |
|
* Number of connection attempts for each server |
|
*/ |
|
var $_connect_attempts; |
|
|
|
// }}} |
|
// }}} |
|
// {{{ methods |
|
// {{{ public functions |
|
// {{{ memcached() |
|
|
|
/** |
|
* Memcache initializer |
|
* |
|
* @param array $args Associative array of settings |
|
* |
|
* @return mixed |
|
* @access public |
|
*/ |
|
function memcached ($args) |
|
{ |
|
$this->set_servers(@$args['servers']); |
|
$this->_debug = @$args['debug']; |
|
$this->stats = array(); |
|
$this->_compress_threshold = @$args['compress_threshold']; |
|
$this->_persistant = array_key_exists('persistant', $args) ? (@$args['persistant']) : false; |
|
$this->_compress_enable = true; |
|
$this->_have_zlib = function_exists("gzcompress"); |
|
|
|
$this->_cache_sock = array(); |
|
$this->_host_dead = array(); |
|
|
|
$this->_timeout_seconds = 1; |
|
$this->_timeout_microseconds = 0; |
|
|
|
$this->_connect_timeout = 0.01; |
|
$this->_connect_attempts = 3; |
|
} |
|
|
|
// }}} |
|
// {{{ add() |
|
|
|
/** |
|
* Adds a key/value to the memcache server if one isn't already set with |
|
* that key |
|
* |
|
* @param string $key Key to set with data |
|
* @param mixed $val Value to store |
|
* @param integer $exp (optional) Time to expire data at |
|
* |
|
* @return boolean |
|
* @access public |
|
*/ |
|
function add ($key, $val, $exp = 0) |
|
{ |
|
return $this->_set('add', $key, $val, $exp); |
|
} |
|
|
|
// }}} |
|
// {{{ decr() |
|
|
|
/** |
|
* Decriment a value stored on the memcache server |
|
* |
|
* @param string $key Key to decriment |
|
* @param integer $amt (optional) Amount to decriment |
|
* |
|
* @return mixed FALSE on failure, value on success |
|
* @access public |
|
*/ |
|
function decr ($key, $amt=1) |
|
{ |
|
return $this->_incrdecr('decr', $key, $amt); |
|
} |
|
|
|
// }}} |
|
// {{{ delete() |
|
|
|
/** |
|
* Deletes a key from the server, optionally after $time |
|
* |
|
* @param string $key Key to delete |
|
* @param integer $time (optional) How long to wait before deleting |
|
* |
|
* @return boolean TRUE on success, FALSE on failure |
|
* @access public |
|
*/ |
|
function delete ($key, $time = 0) |
|
{ |
|
if (!$this->_active) |
|
return false; |
|
|
|
$sock = $this->get_sock($key); |
|
if (!is_resource($sock)) |
|
return false; |
|
|
|
$key = is_array($key) ? $key[1] : $key; |
|
|
|
@$this->stats['delete']++; |
|
$cmd = "delete $key $time\r\n"; |
|
if(!$this->_safe_fwrite($sock, $cmd, strlen($cmd))) |
|
{ |
|
$this->_dead_sock($sock); |
|
return false; |
|
} |
|
$res = trim(fgets($sock)); |
|
|
|
if ($this->_debug) |
|
$this->_debugprint(sprintf("MemCache: delete %s (%s)\n", $key, $res)); |
|
|
|
if ($res == "DELETED") |
|
return true; |
|
return false; |
|
} |
|
|
|
// }}} |
|
// {{{ disconnect_all() |
|
|
|
/** |
|
* Disconnects all connected sockets |
|
* |
|
* @access public |
|
*/ |
|
function disconnect_all () |
|
{ |
|
foreach ($this->_cache_sock as $sock) |
|
fclose($sock); |
|
|
|
$this->_cache_sock = array(); |
|
} |
|
|
|
// }}} |
|
// {{{ enable_compress() |
|
|
|
/** |
|
* Enable / Disable compression |
|
* |
|
* @param boolean $enable TRUE to enable, FALSE to disable |
|
* |
|
* @access public |
|
*/ |
|
function enable_compress ($enable) |
|
{ |
|
$this->_compress_enable = $enable; |
|
} |
|
|
|
// }}} |
|
// {{{ forget_dead_hosts() |
|
|
|
/** |
|
* Forget about all of the dead hosts |
|
* |
|
* @access public |
|
*/ |
|
function forget_dead_hosts () |
|
{ |
|
$this->_host_dead = array(); |
|
} |
|
|
|
// }}} |
|
// {{{ get() |
|
|
|
/** |
|
* Retrieves the value associated with the key from the memcache server |
|
* |
|
* @param string $key Key to retrieve |
|
* |
|
* @return mixed |
|
* @access public |
|
*/ |
|
function get ($key) |
|