Memcached.class.php

Go to the documentation of this file.
00001 <?php
00002 /***************************************************************************
00003  *   Copyright (C) 2004-2008 by Konstantin V. Arkhipov                     *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU Lesser General Public License as        *
00007  *   published by the Free Software Foundation; either version 3 of the    *
00008  *   License, or (at your option) any later version.                       *
00009  *                                                                         *
00010  *   Inspired by the work of Ryan Gilfether <hotrodder@rocketmail.com>     *
00011  *   Copyright (c) 2003, under the GNU GPL license                         *
00012  *                                                                         *
00013  ***************************************************************************/
00014 /* $Id$ */
00015 
00023     final class Memcached extends CachePeer
00024     {
00025         const DEFAULT_PORT      = 11211;
00026         const DEFAULT_HOST      = '127.0.0.1';
00027         const DEFAULT_BUFFER    = 16384;
00028         
00029         private $link       = null;
00030         
00031         private $buffer     = Memcached::DEFAULT_BUFFER;
00032         
00036         public static function create(
00037             $host = Memcached::DEFAULT_HOST,
00038             $port = Memcached::DEFAULT_PORT,
00039             $buffer = Memcached::DEFAULT_BUFFER
00040         )
00041         {
00042             return new Memcached($host, $port, $buffer);
00043         }
00044         
00045         public function __construct(
00046             $host = Memcached::DEFAULT_HOST,
00047             $port = Memcached::DEFAULT_PORT,
00048             $buffer = Memcached::DEFAULT_BUFFER
00049         )
00050         {
00051             $errno = $errstr = null;
00052             
00053             try {
00054                 if ($this->link = @fsockopen($host, $port, $errno, $errstr, 1)) {
00055                     $this->alive = true;
00056                     
00057                     $this->buffer = $buffer;
00058                     
00059                     stream_set_blocking($this->link, true);
00060                 }
00061             } catch (BaseException $e) {/*_*/}
00062         }
00063         
00064         public function __destruct()
00065         {
00066             try {
00067                 fclose($this->link);
00068             } catch (BaseException $e) {/*_*/}
00069         }
00070         
00074         public function clean()
00075         {
00076             if (!$this->link) {
00077                 $this->alive = false;
00078                 return null;
00079             }
00080             
00081             $this->sendRequest("flush_all\r\n");
00082             
00083             // flushing obligatory response - "OK\r\n"
00084             fread($this->link, 4);
00085             
00086             return parent::clean();
00087         }
00088         
00089         public function getList($indexes)
00090         {
00091             if (!$this->link) {
00092                 $this->alive = false;
00093                 return null;
00094             }
00095             
00096             $command = 'get '.implode(' ', $indexes)."\r\n";
00097             
00098             if (!$this->sendRequest($command))
00099                 return null;
00100             
00101             // we can't deserialize objects inside parseGetRequest,
00102             // because of possibility further requests to memcached
00103             // during deserialization - in __wakeup(), for example
00104             return unserialize($this->parseGetRequest(false));
00105         }
00106         
00107         public function increment($key, $value)
00108         {
00109             return $this->changeInteger('incr', $key, $value);
00110         }
00111         
00112         public function decrement($key, $value)
00113         {
00114             return $this->changeInteger('decr', $key, $value);
00115         }
00116         
00117         public function get($index)
00118         {
00119             if (!$this->link) {
00120                 $this->alive = false;
00121                 return null;
00122             }
00123             
00124             $command = "get {$index}\r\n";
00125             
00126             if (!$this->sendRequest($command))
00127                 return null;
00128             
00129             return $this->parseGetRequest(true);
00130         }
00131         
00132         public function delete($index, $time = null)
00133         {
00134             $command =
00135                 $time
00136                     ? "delete {$index} {$time}\r\n"
00137                     : "delete {$index}\r\n";
00138             
00139             if (!$this->sendRequest($command))
00140                 return false;
00141             
00142             try {
00143                 $response = fread($this->link, $this->buffer);
00144             } catch (BaseException $e) {
00145                 return false;
00146             }
00147             
00148             if ($response === "DELETED\r\n")
00149                 return true;
00150             else
00151                 return false;
00152         }
00153         
00154         public function append($key, $data)
00155         {
00156             $packed = serialize($data);
00157             
00158             $length = strlen($packed);
00159             
00160             // flags and exptime are ignored
00161             $command = "append {$key} 0 0 {$length}\r\n{$packed}\r\n";
00162             
00163             if (!$this->sendRequest($command))
00164                 return false;
00165             
00166             $response = fread($this->link, $this->buffer);
00167             
00168             if ($response === "STORED\r\n")
00169                 return true;
00170             
00171             return false;
00172         }
00173         
00174         protected function store(
00175             $method, $index, $value, $expires = Cache::EXPIRES_MINIMUM
00176         )
00177         {
00178             if ($expires === Cache::DO_NOT_CACHE)
00179                 return false;
00180             
00181             $flags = 0;
00182             
00183             if (!is_numeric($value)) {
00184                 if (is_string($value))
00185                     $packed = $value;
00186                 else {
00187                     $packed = serialize($value);
00188                     
00189                     $flags |= 1;
00190                 }
00191                 
00192                 if ($this->compress) {
00193                     $compressed = gzcompress($packed);
00194                     
00195                     if (strlen($compressed) < strlen($packed)) {
00196                         $packed = $compressed;
00197                         $flags |= 2;
00198                         unset($compressed);
00199                     }
00200                 }
00201             } elseif (
00202                 Assert::checkFloat($value)
00203                 && ((int) $value != (float) $value)
00204             ) {
00205                 $packed = serialize($value);
00206                 
00207                 $flags |= 1;
00208             } else
00209                 $packed = $value;
00210             
00211             $lenght = strlen($packed);
00212             
00213             $command = "{$method} {$index} {$flags} {$expires} {$lenght}\r\n{$packed}\r\n";
00214             
00215             if (!$this->sendRequest($command))
00216                 return false;
00217             
00218             $response = fread($this->link, $this->buffer);
00219             
00220             if ($response === "STORED\r\n")
00221                 return true;
00222             
00223             return false;
00224         }
00225         
00226         private function parseGetRequest($single)
00227         {
00228             $result = null;
00229             $index = 0;
00230             
00231             while ($header = fgets($this->link, 8192)) {
00232                 if (
00233                     ($header === "END\r\n")
00234                     || ($header === "ERROR\r\n")
00235                 )
00236                     break;
00237                 
00238                 $array = explode(' ', rtrim($header, "\r\n"), 4);
00239                 
00240                 if (count($array) <> 4)
00241                     continue;
00242                 else
00243                     list(, $key, $flags, $bytes) = $array;
00244                 
00245                 if (
00246                     is_string($key)
00247                     && is_numeric($flags)
00248                     && is_numeric($bytes)
00249                 ) {
00250                     $value = stream_get_contents($this->link, $bytes);
00251                     
00252                     if ($flags & 2)
00253                         $value = gzuncompress($value);
00254                     
00255                     if ($single) {
00256                         fread($this->link, 7); // skip "\r\nEND\r\n"
00257                         
00258                         if ($flags & 1)
00259                             $value = unserialize($value);
00260                         else
00261                             // help in case when 100 was decreased to 99
00262                             // memcached will not honor output lenght then
00263                             $value = rtrim($value);
00264                         
00265                         return $value;
00266                     } else {
00267                         fread($this->link, 2); // skip "\r\n"
00268                         
00269                         $index++;
00270                         
00271                         if (is_numeric($key)) {
00272                             $result .= 'i:'.$key.';';
00273                         } else {
00274                             $result .= 's:'.strlen($key).':"'.$key.'";';
00275                         }
00276                         
00277                         if ($flags & 1)
00278                             $result .= $value;
00279                         elseif (is_numeric($value))
00280                             $result .= 'i:'.$value.';';
00281                         else // string
00282                             $result .= 's:'.$bytes.':"'.$value.'";';
00283                     }
00284                 } else
00285                     break;
00286             }
00287             
00288             if ($single)
00289                 return $result;
00290             else
00291                 return 'a:'.$index.':{'.$result.'}';
00292         }
00293         
00294         private function changeInteger($command, $key, $value)
00295         {
00296             if (!$this->link)
00297                 return null;
00298             
00299             $command = "{$command} {$key} {$value}\r\n";
00300             
00301             if (!$this->sendRequest($command))
00302                 return null;
00303             
00304             try {
00305                 $response = rtrim(fread($this->link, $this->buffer));
00306             } catch (BaseException $e) {
00307                 return null;
00308             }
00309             
00310             if (is_numeric($response))
00311                 return (int) $response;
00312             
00313             return null;
00314         }
00315         
00316         private function sendRequest($command)
00317         {
00318             $commandLenght = strlen($command);
00319             
00320             if ($commandLenght > $this->buffer) {
00321                 $offset = 0;
00322                 while ($offset < $commandLenght) {
00323                     try {
00324                         $result = fwrite(
00325                             $this->link,
00326                             substr($command, $offset, $this->buffer)
00327                         );
00328                     } catch (BaseException $e) {
00329                         return $this->alive = false;
00330                     }
00331                     
00332                     if ($result !== false)
00333                         $offset += $result;
00334                     else
00335                         return false;
00336                 }
00337             } else {
00338                 try {
00339                     return
00340                         fwrite($this->link, $command, $commandLenght) !== false;
00341                 } catch (BaseException $e) {
00342                     return $this->alive = false;
00343                 }
00344             }
00345             
00346             return true;
00347         }
00348     }
00349 ?>