5 * Ryan Gilfether <hotrodder@rocketmail.com>
6 * http://www.gilfether.com
8 * Originally translated from Brad Fitzpatrick's <brad@danga.com> MemCached Perl client
9 * See the memcached website:
10 * http://www.danga.com/memcached/
12 * This module is Copyright (c) 2003 Ryan Gilfether.
13 * All rights reserved.
14 * You may distribute under the terms of the GNU General Public License
15 * This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
22 define("MC_VERSION", "1.0.10");
24 * int, buffer size used for sending and receiving
27 define("MC_BUFFER_SZ", 1024);
29 * MemCached error numbers
31 define("MC_ERR_NOT_ACTIVE", 1001); // no active servers
32 define("MC_ERR_SOCKET_WRITE", 1002); // socket_write() failed
33 define("MC_ERR_SOCKET_READ", 1003); // socket_read() failed
34 define("MC_ERR_SOCKET_CONNECT", 1004); // failed to connect to host
35 define("MC_ERR_DELETE", 1005); // delete() did not recieve DELETED command
36 define("MC_ERR_HOST_FORMAT", 1006); // sock_to_host() invalid host format
37 define("MC_ERR_HOST_DEAD", 1007); // sock_to_host() host is dead
38 define("MC_ERR_GET_SOCK", 1008); // get_sock() failed to find a valid socket
39 define("MC_ERR_SET", 1009); // _set() failed to receive the STORED response
40 define("MC_ERR_GET_KEY", 1010); // _load_items no values returned for key(s)
41 define("MC_ERR_LOADITEM_END", 1011); // _load_items failed to receive END response
42 define("MC_ERR_LOADITEM_BYTES", 1012); // _load_items bytes read larger than bytes available
46 * MemCached PHP client Class.
48 * Communicates with the MemCached server, and executes the MemCached protocol
49 * MemCached available at http://www.danga.com/memcached
51 * @author Ryan Gilfether <ryan@gilfether.com>
52 * @package MemCachedClient
59 * array of servers no long available
64 * array of open sockets
69 * determine if debugging is either on or off
74 * array of servers to attempt to use, "host:port" string format
79 * count of currently active connections to servers
84 * error code if one is set
89 * string describing error
94 * size of val to force compression; 0 turns off; defaults 1
99 * temp flag to turn compression on/off; defaults on
102 var $comp_active = 1;
105 * array that contains parsed out buckets
114 * Creates a new MemCachedClient object
115 * Takes one parameter, a array of options. The most important key is
116 * $options["servers"], but that can also be set later with the set_servers()
117 * method. The servers must be an array of hosts, each of which is
118 * either a scalar of the form <10.0.0.10:11211> or an array of the
119 * former and an integer weight value. (the default weight if
120 * unspecified is 1.) It's recommended that weight values be kept as low
121 * as possible, as this module currently allocates memory for bucket
122 * distribution proportional to the total host weights.
123 * $options["debug"] turns the debugging on if set to true
126 * @param array $option an array of servers and debug status
127 * @return object MemCachedClient the new MemCachedClient object
129 function MemCachedClient($options = 0)
131 if(is_array($options))
133 $this->set_servers($options["servers"]);
134 $this->debug
= $options["debug"];
135 $this->compress
= $options["compress"];
136 $this->cache_sock
= array();
145 * sets up the list of servers and the ports to connect to
146 * takes an array of servers in the same format as in the constructor
149 * @param array $servers array of servers in the format described in the constructor
151 function set_servers($servers)
153 $this->servers
= $servers;
154 $this->active
= count($this->servers
);
159 * if $do_debug is set to true, will print out
160 * debugging info, else debug is turned off
163 * @param bool $do_debug set to true to turn debugging on, false to turn off
165 function set_debug($do_debug)
167 $this->debug
= $do_debug;
172 * remove all cached hosts that are no longer good
176 function forget_dead_hosts()
178 unset($this->host_dead
);
183 * disconnects from all servers
187 function disconnect_all()
189 foreach($this->cache_sock
as $sock)
192 unset($this->cache_sock
);
198 * removes the key from the MemCache
199 * $time is the amount of time in seconds (or Unix time) until which
200 * the client wishes the server to refuse "add" and "replace" commands
201 * with this key. For this amount of item, the item is put into a
202 * delete queue, which means that it won't possible to retrieve it by
203 * the "get" command, but "add" and "replace" command with this key
204 * will also fail (the "set" command will succeed, however). After the
205 * time passes, the item is finally deleted from server memory.
206 * The parameter $time is optional, and, if absent, defaults to 0
207 * (which means that the item will be deleted immediately and further
208 * storage commands with this key will succeed).
209 * Possible errors set are:
212 * MC_ERR_SOCKET_WRITE
217 * @param string $key the key to delete
218 * @param timestamp $time optional, the amount of time server will refuse commands on key
219 * @return bool TRUE on success, FALSE if key does not exist
221 function delete($key, $time = 0)
225 $this->errno
= MC_ERR_NOT_ACTIVE
;
226 $this->errstr
= "No active servers are available";
229 $this->_debug("delete(): There are no active servers available.");
234 $sock = $this->get_sock($key);
236 if(!is_resource($sock))
238 $this->errno
= MC_ERR_GET_SOCK
;
239 $this->errstr
= "Unable to retrieve a valid socket.";
242 $this->_debug("delete(): get_sock() returned an invalid socket.");
250 $cmd = "delete $key $time\r\n";
251 $cmd_len = strlen($cmd);
254 // now send the command
255 while($offset < $cmd_len)
257 $result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ
), MC_BUFFER_SZ
);
259 if($result !== FALSE)
261 else if($offset < $cmd_len)
263 $this->errno
= MC_ERR_SOCKET_WRITE
;
264 $this->errstr
= "Failed to write to socket.";
268 $sockerr = socket_last_error($sock);
269 $this->_debug("delete(): socket_write() returned FALSE. Socket Error $sockerr: ".socket_strerror($sockerr));
276 // now read the server's response
277 if(($retval = socket_read($sock, MC_BUFFER_SZ
, PHP_NORMAL_READ
)) === FALSE)
279 $this->errno
= MC_ERR_SOCKET_READ
;
280 $this->errstr
= "Failed to read from socket.";
284 $sockerr = socket_last_error($sock);
285 $this->_debug("delete(): socket_read() returned FALSE. Socket Error $sockerr: ".socket_strerror($sockerr));
291 // remove the \r\n from the end
292 $retval = rtrim($retval);
294 // now read the server's response
295 if($retval == "DELETED")
299 // something went wrong, create the error
300 $this->errno
= MC_ERR_DELETE
;
301 $this->errstr
= "Failed to receive DELETED response from server.";
304 $this->_debug("delete(): Failed to receive DELETED response from server. Received $retval instead.");
312 * Like set(), but only stores in memcache if the key doesn't already exist.
313 * Possible errors set are:
316 * MC_ERR_SOCKET_WRITE
321 * @param string $key the key to set
322 * @param mixed $val the value of the key
323 * @param timestamp $exptime optional, the to to live of the key
324 * @return bool TRUE on success, else FALSE
326 function add($key, $val, $exptime = 0)
328 return $this->_set("add", $key, $val, $exptime);
333 * Like set(), but only stores in memcache if the key already exists.
334 * returns TRUE on success else FALSE
335 * Possible errors set are:
338 * MC_ERR_SOCKET_WRITE
343 * @param string $key the key to set
344 * @param mixed $val the value of the key
345 * @param timestamp $exptime optional, the to to live of the key
346 * @return bool TRUE on success, else FALSE
348 function replace($key, $val, $exptime = 0)
350 return $this->_set("replace", $key, $val, $exptime);
355 * Unconditionally sets a key to a given value in the memcache. Returns true
356 * if it was stored successfully.
357 * The $key can optionally be an arrayref, with the first element being the
358 * hash value, as described above.
359 * Possible errors set are:
362 * MC_ERR_SOCKET_WRITE
367 * @param string $key the key to set
368 * @param mixed $val the value of the key
369 * @param timestamp $exptime optional, the to to live of the key
370 * @return bool TRUE on success, else FALSE
372 function set($key, $val, $exptime = 0)
374 return $this->_set("set", $key, $val, $exptime);
379 * Retrieves a key from the memcache. Returns the value (automatically
380 * unserialized, if necessary) or FALSE if it fails.
381 * The $key can optionally be an array, with the first element being the
382 * hash value, if you want to avoid making this module calculate a hash
383 * value. You may prefer, for example, to keep all of a given user's
384 * objects on the same memcache server, so you could use the user's
385 * unique id as the hash value.
386 * Possible errors set are:
390 * @param string $key the key to retrieve
391 * @return mixed the value of the key, FALSE on error
395 $val =& $this->get_multi($key);
399 $this->errno
= MC_ERR_GET_KEY
;
400 $this->errstr
= "No value found for key $key";
403 $this->_debug("get(): No value found for key $key");
413 * just like get(), but takes an array of keys, returns FALSE on error
414 * Possible errors set are:
418 * @param array $keys the keys to retrieve
419 * @return array the value of each key, FALSE on error
421 function get_multi($keys)
423 $sock_keys = array();
429 $this->errno
= MC_ERR_NOT_ACTIVE
;
430 $this->errstr
= "No active servers are available";
433 $this->_debug("get_multi(): There are no active servers available.");
446 $sock = $this->get_sock($k);
450 $k = is_array($k) ?
$k[1] : $k;
452 if(@!is_array($sock_keys[$sock]))
453 $sock_keys[$sock] = array();
455 // if $sock_keys[$sock] doesn't exist, create it
456 if(!$sock_keys[$sock])
459 $sock_keys[$sock][] = $k;
463 if(!is_array($socks))
469 foreach($socks as $s)
471 $this->_load_items($s, $val, $sock_keys[$sock]);
476 while(list($k, $v) = @each
($val))
477 $this->_debug("MemCache: got $k = $v\n");
485 * Sends a command to the server to atomically increment the value for
486 * $key by $value, or by 1 if $value is undefined. Returns FALSE if $key
487 * doesn't exist on server, otherwise it returns the new value after
488 * incrementing. Value should be zero or greater. Overflow on server
489 * is not checked. Be aware of values approaching 2**32. See decr.
490 * ONLY WORKS WITH NUMERIC VALUES
491 * Possible errors set are:
494 * MC_ERR_SOCKET_WRITE
498 * @param string $key the keys to increment
499 * @param int $value the amount to increment the key bye
500 * @return int the new value of the key, else FALSE
502 function incr($key, $value = 1)
504 return $this->_incrdecr("incr", $key, $value);
509 * Like incr, but decrements. Unlike incr, underflow is checked and new
510 * values are capped at 0. If server value is 1, a decrement of 2
512 * ONLY WORKS WITH NUMERIC VALUES
513 * Possible errors set are:
516 * MC_ERR_SOCKET_WRITE
520 * @param string $key the keys to increment
521 * @param int $value the amount to increment the key bye
522 * @return int the new value of the key, else FALSE
524 function decr($key, $value = 1)
526 return $this->_incrdecr("decr", $key, $value);
531 * When a function returns FALSE, an error code is set.
532 * This funtion will return the error code.
536 * @return int the value of the last error code
545 * Returns a string describing the error set in error()
549 * @return int a string describing the error code given
551 function error_string()
553 return $this->errstr
;
558 * Resets the error number and error string
562 function error_clear()
571 * temporarily sets compression on or off
572 * turning it off, and then back on will result in the compression threshold going
573 * back to the original setting from $options
574 * @param int $setting setting of compression (0=off|1=on)
577 function set_compression($setting=1) {
579 $this->comp_active
= 1;
581 $this->comp_active
= 0;
593 * connects to a server
594 * The $host may either a string int the form of host:port or an array of the
595 * former and an integer weight value. (the default weight if
596 * unspecified is 1.) See the constructor for details
597 * Possible errors set are:
600 * MC_ERR_SOCKET_CONNECT
603 * @param mixed $host either an array or a string
604 * @return resource the socket of the new connection, else FALSE
606 function sock_to_host($host)
609 $host = array_shift($host);
613 // seperate the ip from the port, index 0 = ip, index 1 = port
614 $conn = explode(":", $host);
615 if(count($conn) != 2)
617 $this->errno
= MC_ERR_HOST_FORMAT
;
618 $this->errstr
= "Host address was not in the format of host:port";
621 $this->_debug("sock_to_host(): Host address was not in the format of host:port");
626 if(@($this->host_dead
[$host] && $this->host_dead
[$host] > $now) ||
627 @($this->host_dead
[$conn[0]] && $this->host_dead
[$conn[0]] > $now))
629 $this->errno
= MC_ERR_HOST_DEAD
;
630 $this->errstr
= "Host $host is not available.";
633 $this->_debug("sock_to_host(): Host $host is not available.");
638 // connect to the server, if it fails, add it to the host_dead below
639 $sock = socket_create (AF_INET
, SOCK_STREAM
, getprotobyname("TCP"));
641 // we need surpress the error message if a connection fails
642 if(!@socket_connect
($sock, $conn[0], $conn[1]))
644 $this->host_dead
[$host]=$this->host_dead
[$conn[0]]=$now+
60+
intval(rand(0, 10));
646 $this->errno
= MC_ERR_SOCKET_CONNECT
;
647 $this->errstr
= "Failed to connect to ".$conn[0].":".$conn[1];
650 $this->_debug("sock_to_host(): Failed to connect to ".$conn[0].":".$conn[1]);
655 // success, add to the list of sockets
656 $cache_sock[$host] = $sock;
663 * retrieves the socket associated with a key
664 * Possible errors set are:
669 * @param string $key the key to retrieve the socket from
670 * @return resource the socket of the connection, else FALSE
672 function get_sock($key)
676 $this->errno
= MC_ERR_NOT_ACTIVE
;
677 $this->errstr
= "No active servers are available";
680 $this->_debug("get_sock(): There are no active servers available.");
685 $hv = is_array($key) ?
intval($key[0]) : $this->_hashfunc($key);
689 $bu = $this->buckets
= array();
691 foreach($this->servers
as $v)
695 for($i = 1; $i <= $v[1]; ++
$i)
702 $this->buckets
= $bu;
705 $real_key = is_array($key) ?
$key[1] : $key;
709 $host = @$this->buckets
[$hv %
count($this->buckets
)];
710 $sock = $this->sock_to_host($host);
712 if(is_resource($sock))
715 $hv +
= $this->_hashfunc($tries.$real_key);
719 $this->errno
= MC_ERR_GET_SOCK
;
720 $this->errstr
= "Unable to retrieve a valid socket.";
723 $this->_debug("get_sock(): Unable to retrieve a valid socket.");
730 * increments or decrements a numerical value in memcached. this function is
731 * called from incr() and decr()
732 * ONLY WORKS WITH NUMERIC VALUES
733 * Possible errors set are:
736 * MC_ERR_SOCKET_WRITE
740 * @param string $cmdname the command to send, either incr or decr
741 * @param string $key the key to perform the command on
742 * @param mixed $value the value to incr or decr the key value by
743 * @return int the new value of the key, FALSE if something went wrong
745 function _incrdecr($cmdname, $key, $value)
749 $this->errno
= MC_ERR_NOT_ACTIVE
;
750 $this->errstr
= "No active servers are available";
753 $this->_debug("_incrdecr(): There are no active servers available.");
758 $sock = $this->get_sock($key);
759 if(!is_resource($sock))
761 $this->errno
= MC_ERR_GET_SOCK
;
762 $this->errstr
= "Unable to retrieve a valid socket.";
765 $this->_debug("_incrdecr(): Invalid socket returned by get_sock().");
773 $cmd = "$cmdname $key $value\r\n";
774 $cmd_len = strlen($cmd);
777 // write the command to the server
778 while($offset < $cmd_len)
780 $result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ
), MC_BUFFER_SZ
);
782 if($result !== FALSE)
784 else if($offset < $cmd_len)
786 $this->errno
= MC_ERR_SOCKET_WRITE
;
787 $this->errstr
= "Failed to write to socket.";
791 $sockerr = socket_last_error($sock);
792 $this->_debug("_incrdecr(): socket_write() returned FALSE. Error $errno: ".socket_strerror($sockerr));
799 // now read the server's response
800 if(($retval = socket_read($sock, MC_BUFFER_SZ
, PHP_NORMAL_READ
)) === FALSE)
802 $this->errno
= MC_ERR_SOCKET_READ
;
803 $this->errstr
= "Failed to read from socket.";
807 $sockerr = socket_last_error($sock);
808 $this->_debug("_incrdecr(): socket_read() returned FALSE. Socket Error $errno: ".socket_strerror($sockerr));
814 // strip the /r/n from the end and return value
815 return trim($retval);
819 * sends the command to the server
820 * Possible errors set are:
823 * MC_ERR_SOCKET_WRITE
828 * @param string $cmdname the command to send, either incr or decr
829 * @param string $key the key to perform the command on
830 * @param mixed $value the value to set the key to
831 * @param timestamp $exptime expiration time of the key
832 * @return bool TRUE on success, else FALSE
834 function _set($cmdname, $key, $val, $exptime = 0)
838 $this->errno
= MC_ERR_NOT_ACTIVE
;
839 $this->errstr
= "No active servers are available";
842 $this->_debug("_set(): No active servers are available.");
847 $sock = $this->get_sock($key);
848 if(!is_resource($sock))
850 $this->errno
= MC_ERR_GET_SOCK
;
851 $this->errstr
= "Unable to retrieve a valid socket.";
854 $this->_debug("_set(): Invalid socket returned by get_sock().");
860 $key = is_array($key) ?
$key[1] : $key;
864 // if the value is not scalar, we need to serialize it
867 $val = serialize($val);
871 if (($this->compress_active
) && ($this->compress
> 0) && (strlen($val) > $this->compress
)) {
872 $this->_debug("_set(): compressing data. size in:".strlen($val));
873 $cval=gzcompress($val);
874 $this->_debug("_set(): done compressing data. size out:".strlen($cval));
875 if ((strlen($cval) < strlen($val)) && (strlen($val) - strlen($cval) > 2048)){
883 if (!is_int($exptime))
886 // send off the request
887 $cmd = "$cmdname $key $flags $exptime $len\r\n$val\r\n";
888 $cmd_len = strlen($cmd);
891 // write the command to the server
892 while($offset < $cmd_len)
894 $result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ
), MC_BUFFER_SZ
);
896 if($result !== FALSE)
898 else if($offset < $cmd_len)
900 $this->errno
= MC_ERR_SOCKET_WRITE
;
901 $this->errstr
= "Failed to write to socket.";
905 $errno = socket_last_error($sock);
906 $this->_debug("_set(): socket_write() returned FALSE. Error $errno: ".socket_strerror($errno));
913 // now read the server's response
914 if(($l_szResponse = socket_read($sock, 6, PHP_NORMAL_READ
)) === FALSE)
916 $this->errno
= MC_ERR_SOCKET_READ
;
917 $this->errstr
= "Failed to read from socket.";
921 $errno = socket_last_error($sock);
922 $this->_debug("_set(): socket_read() returned FALSE. Error $errno: ".socket_strerror($errno));
928 if($l_szResponse == "STORED")
931 $this->_debug("MemCache: $cmdname $key = $raw_val");
936 $this->errno
= MC_ERR_SET
;
937 $this->errstr
= "Failed to receive the STORED response from the server.";
940 $this->_debug("_set(): Did not receive STORED as the server response! Received $l_szResponse instead.");
947 * retrieves the value, and returns it unserialized
948 * Possible errors set are:
949 * MC_ERR_SOCKET_WRITE
952 * MC_ERR_LOADITEM_END
953 * MC_ERR_LOADITEM_BYTES
956 * @param resource $sock the socket to connection we are retriving from
957 * @param array $val reference to the values retrieved
958 * @param mixed $sock_keys either a string or an array of keys to retrieve
959 * @return array TRUE on success, else FALSE
961 function _load_items($sock, &$val, $sock_keys)
966 if(!is_array($sock_keys))
972 foreach($sock_keys as $sk)
976 $cmd_len = strlen($cmd);
979 // write the command to the server
980 while($offset < $cmd_len)
982 $result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ
), MC_BUFFER_SZ
);
984 if($result !== FALSE)
986 else if($offset < $cmd_len)
988 $this->errno
= MC_ERR_SOCKET_WRITE
;
989 $this->errstr
= "Failed to write to socket.";
993 $errno = socket_last_error($sock);
994 $this->_debug("_load_items(): socket_write() returned FALSE. Error $errno: ".socket_strerror($errno));
1003 $flags_array = array();
1005 // now read the response from the server
1006 while($line = socket_read($sock, MC_BUFFER_SZ
, PHP_BINARY_READ
))
1008 // check for a socket_read error
1011 $this->errno
= MC_ERR_SOCKET_READ
;
1012 $this->errstr
= "Failed to read from socket.";
1016 $errno = socket_last_error($sock);
1017 $this->_debug("_load_items(): socket_read() returned FALSE. Error $errno: ".socket_strerror($errno));
1025 $header = substr($line, 0, strpos($line, "\r\n"));
1026 $matches = explode(" ", $header);
1028 if(is_string($matches[1]) && is_numeric($matches[2]) && is_numeric($matches[3]))
1031 $flags = $matches[2];
1035 $flags_array[$rk] = $flags;
1037 $len_array[$rk] = $len;
1040 // get the left over data after the header is read
1041 $line = substr($line, strpos($line, "\r\n")+
2, strlen($line));
1045 $this->errno
= MC_ERR_GET_KEY
;
1046 $this->errstr
= "Requested key(s) returned no values.";
1048 // something went wrong, we never recieved the header
1050 $this->_debug("_load_items(): Requested key(s) returned no values.");
1056 // skip over the extra return or newline
1057 if($line == "\r" ||
$line == "\n")
1060 $bytes_read +
= strlen($line);
1063 // we read the all of the data, take in account
1064 // for the /r/nEND/r/n
1065 if($bytes_read == ($len +
7))
1067 $end = substr($buf, $len+
2, 3);
1070 $val[$rk] = substr($buf, 0, $len);
1072 foreach($sock_keys as $sk)
1074 if(!isset($val[$sk]))
1077 if(strlen($val[$sk]) != $len_array[$sk])
1079 if(@$flags_array[$sk] & 2)
1080 $val[$sk] = gzuncompress($val[$sk]);
1082 if(@$flags_array[$sk] & 1)
1083 $val[$sk] = unserialize($val[$sk]);
1090 $this->errno
= MC_ERR_LOADITEM_END
;
1091 $this->errstr
= "Failed to receive END response from server.";
1094 $this->_debug("_load_items(): Failed to receive END. Received $end instead.");
1100 // take in consideration for the "\r\nEND\r\n"
1101 if($bytes_read > ($len +
7))
1103 $this->errno
= MC_ERR_LOADITEM_BYTES
;
1104 $this->errstr
= "Bytes read from server greater than size of data.";
1107 $this->_debug("_load_items(): Bytes read is greater than requested data size.");
1123 function _hashfunc($num)
1125 $hash = sprintf("%u",crc32($num));
1131 * function that can be overridden to handle debug output
1132 * by default debug info is print to the screen
1135 * @param $text string to output debug info
1137 function _debug($text)
1139 print $text . "\r\n";