<?php
/**
* PHP memcached client class
*
* For build develop environment in windows using memcached.
*
* @package memcached-client
* @copyright Copyright 2013-2014, Fwolf
* @license http://opensource.org/licenses/mit-license MIT
* @version 1.2.0
*/
class Memcached
{
// Predefined Constants
// See: http://php.net/manual/en/memcached.constants.php
// Defined in php_memcached.c
const OPT_COMPRESSION = -1001;
const OPT_SERIALIZER = -1003;
// enum memcached_serializer in php_memcached
const SERIALIZER_PHP = 1;
const SERIALIZER_IGBINARY = 2;
const SERIALIZER_JSON = 3;
// Defined in php_memcached.c
const OPT_PREFIX_KEY = -1002;
// enum memcached_behavior_t in libmemcached
const OPT_HASH = 2; //MEMCACHED_BEHAVIOR_HASH
// enum memcached_hash_t in libmemcached
const HASH_DEFAULT = 0;
const HASH_MD5 = 1;
const HASH_CRC = 2;
const HASH_FNV1_64 = 3;
const HASH_FNV1A_64 = 4;
const HASH_FNV1_32 = 5;
const HASH_FNV1A_32 = 6;
const HASH_HSIEH = 7;
const HASH_MURMUR = 8;
// enum memcached_behavior_t in libmemcached
const OPT_DISTRIBUTION = 9; // MEMCACHED_BEHAVIOR_DISTRIBUTION
// enum memcached_server_distribution_t in libmemcached
const DISTRIBUTION_MODULA = 0;
const DISTRIBUTION_CONSISTENT = 1;
// enum memcached_behavior_t in libmemcached
const OPT_LIBKETAMA_COMPATIBLE = 16; // MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED
const OPT_BUFFER_WRITES = 10; // MEMCACHED_BEHAVIOR_BUFFER_REQUESTS
const OPT_BINARY_PROTOCOL = 18; // MEMCACHED_BEHAVIOR_BINARY_PROTOCOL
const OPT_NO_BLOCK = 0; // MEMCACHED_BEHAVIOR_NO_BLOCK
const OPT_TCP_NODELAY = 1; // MEMCACHED_BEHAVIOR_TCP_NODELAY
const OPT_SOCKET_SEND_SIZE = 4; // MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE
const OPT_SOCKET_RECV_SIZE = 5; // MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE
const OPT_CONNECT_TIMEOUT = 14; // MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT
const OPT_RETRY_TIMEOUT = 15; // MEMCACHED_BEHAVIOR_RETRY_TIMEOUT
const OPT_SEND_TIMEOUT = 19; // MEMCACHED_BEHAVIOR_SND_TIMEOUT
const OPT_RECV_TIMEOUT = 20; // MEMCACHED_BEHAVIOR_RCV_TIMEOUT
const OPT_POLL_TIMEOUT = 8; // MEMCACHED_BEHAVIOR_POLL_TIMEOUT
const OPT_CACHE_LOOKUPS = 6; // MEMCACHED_BEHAVIOR_CACHE_LOOKUPS
const OPT_SERVER_FAILURE_LIMIT = 21; // MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT
// In php_memcached config, define HAVE_MEMCACHED_IGBINARY default 1,
// then use ifdef define HAVE_IGBINARY to 1.
const HAVE_IGBINARY = 1;
// In php_memcached config, define HAVE_JSON_API default 1,
// then use ifdef define HAVE_JSON to 1.
const HAVE_JSON = 1;
// Defined in php_memcached.c, (1<<0)
const GET_PRESERVE_ORDER = 1;
// enum memcached_return_t in libmemcached
const RES_SUCCESS = 0; // MEMCACHED_SUCCESS
const RES_FAILURE = 1; // MEMCACHED_FAILURE
const RES_HOST_LOOKUP_FAILURE = 2; // MEMCACHED_HOST_LOOKUP_FAILURE
const RES_UNKNOWN_READ_FAILURE = 7; // MEMCACHED_UNKNOWN_READ_FAILURE
const RES_PROTOCOL_ERROR = 8; // MEMCACHED_PROTOCOL_ERROR
const RES_CLIENT_ERROR = 9; // MEMCACHED_CLIENT_ERROR
const RES_SERVER_ERROR = 10; // MEMCACHED_SERVER_ERROR
const RES_WRITE_FAILURE = 5; // MEMCACHED_WRITE_FAILURE
const RES_DATA_EXISTS = 12; // MEMCACHED_DATA_EXISTS
const RES_NOTSTORED = 14; // MEMCACHED_NOTSTORED
const RES_NOTFOUND = 16; // MEMCACHED_NOTFOUND
const RES_PARTIAL_READ = 18; // MEMCACHED_PARTIAL_READ
const RES_SOME_ERRORS = 19; // MEMCACHED_SOME_ERRORS
const RES_NO_SERVERS = 20; // MEMCACHED_NO_SERVERS
const RES_END = 21; // MEMCACHED_END
const RES_ERRNO = 26; // MEMCACHED_ERRNO
const RES_BUFFERED = 32; // MEMCACHED_BUFFERED
const RES_TIMEOUT = 31; // MEMCACHED_TIMEOUT
const RES_BAD_KEY_PROVIDED = 33; // MEMCACHED_BAD_KEY_PROVIDED
const RES_CONNECTION_SOCKET_CREATE_FAILURE = 11; // MEMCACHED_CONNECTION_SOCKET_CREATE_FAILURE
// Defined in php_memcached.c
const RES_PAYLOAD_FAILURE = -1001;
/**
* Dummy option array
*
* @var array
*/
protected $option = array(
Memcached::OPT_COMPRESSION => true,
Memcached::OPT_SERIALIZER => Memcached::SERIALIZER_PHP,
Memcached::OPT_PREFIX_KEY => '',
Memcached::OPT_HASH => Memcached::HASH_DEFAULT,
Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_MODULA,
Memcached::OPT_LIBKETAMA_COMPATIBLE => false,
Memcached::OPT_BUFFER_WRITES => false,
Memcached::OPT_BINARY_PROTOCOL => false,
Memcached::OPT_NO_BLOCK => false,
Memcached::OPT_TCP_NODELAY => false,
// This two is a value by guess
Memcached::OPT_SOCKET_SEND_SIZE => 32767,
Memcached::OPT_SOCKET_RECV_SIZE => 65535,
Memcached::OPT_CONNECT_TIMEOUT => 1000,
Memcached::OPT_RETRY_TIMEOUT => 0,
Memcached::OPT_SEND_TIMEOUT => 0,
Memcached::OPT_RECV_TIMEOUT => 0,
Memcached::OPT_POLL_TIMEOUT => 1000,
Memcached::OPT_CACHE_LOOKUPS => false,
Memcached::OPT_SERVER_FAILURE_LIMIT => 0,
);
/**
* Last result code
*
* @var int
*/
protected $resultCode = 0;
/**
* Last result message
*
* @var string
*/
protected $resultMessage = '';
/**
* Server list array/pool
*
* I added array index.
*
* array (
* host:port:weight => array(
* host,
* port,
* weight,
* )
* )
*
* @var array
*/
protected $server = array();
/**
* Socket connect handle
*
* Point to last successful connect, ignore others
* @var resource
*/
protected $socket = null;
public function getVersion() {
return ['localhost:11211' => '1.4.5'];
}
//may check: https://raw.githubusercontent.com/GoogleCloudPlatform/python-compat-runtime/master/appengine-compat/exported_appengine_sdk/php/sdk/google/appengine/runtime/Memcached.php
public function setMulti() {
die('TODO');
}
public function getMulti() {
//TODO
die('TODO');
}
/**
* Add a serer to the server pool
*
* @param string $host
* @param int $port
* @param int $weight
* @return boolean
*/
public function addServer($host, $port = 11211, $weight = 0)
{
$key = $this->getServerKey($host, $port, $weight);
if (isset($this->server[$key])) {
// Dup
$this->resultCode = Memcached::RES_FAILURE;
$this->resultMessage = 'Server duplicate.';
return false;
} else {
$this->server[$key] = array(
'host' => $host,
'port' => $port,
'weight' => $weight,
);
$this->connect();
return true;
}
}
/**
* Add multiple servers to the server pool
*
* @param array $servers
* @return boolean
*/
public function addServers($servers)
{
foreach ((array)$servers as $svr) {
$host = array_shift($svr);
$port = array_shift($svr);
if (is_null($port)) {
$port = 11211;
}
$weight = array_shift($svr);
if (is_null($weight)) {
$weight = 0;
}
$this->addServer($host, $port, $weight);
}
return true;
}
/**
* Connect to memcached server
*
* @return boolean
*/
protected function connect()
{
$rs = false;
foreach ((array)$this->server as $svr) {
$error = 0;
$errstr = '';
$rs = @fsockopen($svr['host'], $svr['port'], $error, $errstr);
if ($rs) {
$this->socket = $rs;
} else {
$key = $this->getServerKey(
$svr['host'],
$svr['port'],
$svr['weight']
);
$s = "Connect to $key error:" . PHP_EOL .
" [$error] $errstr";
error_log($s);
}
}
if (is_null($this->socket)) {
$this->resultCode = Memcached::RES_FAILURE;
$this->resultMessage = 'No server avaliable.';
return false;
} else {
$this->resultCode = Memcached::RES_SUCCESS;
$this->resultMessage = '';
return true;
}
}
/**
* Delete an item
*
* @param string $key
* @param int $time Ignored
* @return boolean
*/
public function delete($key, $time = 0)
{
$keyString = $this->getKey($key);
$this->writeSocket("delete $keyString");
$s = $this->readSocket();
if ('DELETED' == $s) {
$this->resultCode = Memcached::RES_SUCCESS;
$this->resultMessage = '';
return true;
} else {
$this->resultCode = Memcached::RES_NOTFOUND;
$this->resultMessage = 'Delete fail, key not exists.';
return false;
}
}
/**
* Retrieve an item
*
* @param string $key
* @param callable $cache_cb Ignored
* @param float $cas_token Ignored
* @return mixed
*/
public function get($key, $cache_cb = null, $cas_token = null)
{
$keyString = $this->getKey($key);
$this->writeSocket("get $keyString");
$s = $this->readSocket();
if (is_null($s) || 'VALUE' != substr($s, 0, 5)) {
$this->resultCode = Memcached::RES_FAILURE;
$this->resultMessage = 'Get fail.';
return false;
} else {
$s_result = '';
$s = $this->readSocket();
while ('END' != $s) {
$s_result .= $s;
$s = $this->readSocket();
}
$this->resultCode = Memcached::RES_SUCCESS;
$this->resultMessage = '';
return unserialize($s_result);
}
}
/**
* Get item key
*
* @param string $key
* @return string
*/
public function getKey($key)
{
return addslashes($this->option[Memcached::OPT_PREFIX_KEY]) . $key;
}
/**
* Get a memcached option value
*
* @param int $option
* @return mixed
*/
public function getOption($option)
{
if (isset($this->option[$option])) {
$this->resultCode = Memcached::RES_SUCCESS;
$this->resultMessage = '';
return $this->option[$option];
} else {
$this->resultCode = Memcached::RES_FAILURE;
$this->resultMessage = 'Option not seted.';
return false;
}
}
/**
* Return the result code of the last operation
*
* @return int
*/
public function getResultCode()
{
return $this->resultCode;
}
/**
* Return the message describing the result of the last opteration
*
* @return string
*/
public function getResultMessage()
{
return $this->resultMessage;
}
/**
* Get key of server array
*
* @param string $host
* @param int $port
* @param int $weight
* @return string
*/
protected function getServerKey($host, $port = 11211, $weight = 0)
{
return "$host:$port:$weight";
}
/**
* Get list array of servers
*
* @see $server
* @return array
*/
public function getServerList()
{
return $this->server;
}
/**
* Read from socket
*
* @return string|null
*/
protected function readSocket()
{
if (is_null($this->socket)) {
return null;
}
return trim(fgets($this->socket));
}
/**
* Store an item
*
* @param string $key
* @param mixed $val
* @param int $expt
* @return boolean
*/
public function set($key, $val, $expt = 0)
{
$valueString = serialize($val);
$keyString = $this->getKey($key);
$this->writeSocket(
"set $keyString 0 $expt " . strlen($valueString)
);
$s = $this->writeSocket($valueString, true);
if ('STORED' == $s) {
$this->resultCode = Memcached::RES_SUCCESS;
$this->resultMessage = '';
return true;
} else {
$this->resultCode = Memcached::RES_FAILURE;
$this->resultMessage = 'Set fail.';
return false;
}
}
/**
* Set a memcached option
*
* @param int $option
* @param mixed $value
* @return boolean
*/
public function setOption($option, $value)
{
$this->option[$option] = $value;
return true;
}
/**
* Set memcached options
*
* @param array $options
* @return bollean
*/
public function setOptions($options)
{
$this->option = array_merge($this->option, $options);
return true;
}
/**
* Increment numeric item's value
*
* @param string $key The key of the item to increment.
* @param int $offset The amount by which to increment the item's value.
* @param int $initial_value The value to set the item to if it doesn't currently exist.
* @param int $expiry The expiry time to set on the item.
*
* @return mixed Returns new item's value on success or FALSE on failure.
*/
public function increment($key, $offset = 1, $initial_value = 0, $expiry = 0)
{
if (($prevVal = $this->get($key))) {
if (!is_numeric($prevVal)) {
return false;
}
$newVal = $prevVal + $offset;
} else {
$newVal = $initial_value;
}
$this->set($key, $newVal, $expiry);
return $newVal;
}
/**
* Write data to socket
*
* @param string $cmd
* @param boolean $result Need result/response
* @return mixed
*/
protected function writeSocket($cmd, $result = false)
{
if (is_null($this->socket)) {
return false;
}
fwrite($this->socket, $cmd . "\r\n");
if (true == $result) {
return $this->readSocket();
}
return true;
}
}