├── .gitignore ├── README.md ├── index-wp-redis.php ├── index.php └── wp-redis-cache ├── cache.php ├── options.php ├── predis5.2.php └── readme.txt /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | 217 | *.zip 218 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Wp Redis Cache 2 | 3 | Cache Wordpress using Redis, the fastest way to date to cache Wordpress. 4 | 5 | ### Requirements 6 | ------ 7 | * [Wordpress](http://wordpress.org) - CMS framework/blogging system 8 | * [Redis](http://redis.io/) - Key Value in memory caching 9 | * [Predis](https://github.com/nrk/predis) - PHP api for Redis 10 | 11 | ### Installation 12 | ------ 13 | Install Redis, must have root access to your machine. On debian it's as simple as: 14 | ```bash 15 | sudo apt-get install redis-server 16 | ``` 17 | On other systems please refer to the [Redis website](http://redis.io/). 18 | 19 | You can install the pecl extension (faster) 20 | ``` 21 | apt-get install php5-redis 22 | ``` 23 | If you don't have the pecl extension installed it will default to use [Predis](https://github.com/nrk/predis). 24 | 25 | Move the folder wp-redis-cache to the plugin directory and activate the plugin. In the admin section you can set how long you will cache the post for. By default it will cache the post for 12 hours. 26 | Note: This plugin is optional and is used to refresh the cache after you update a post/page. 27 | 28 | Move the `index-wp-redis.php` to the root/base Wordpress directory. 29 | 30 | Move the `index.php` to the root/base Wordpress directory. Or manually change the `index.php` to: 31 | 32 | ```php 33 | 36 | ``` 37 | In `index-wp-redis.php` change `$websiteIp` to the IP of your server. If you want to use sockets, change `$sockets` to `true` and enter the path of your socket in `$redis_server`. 38 | 39 | *Note: Sometimes when you upgrade Wordpress it will replace over your `index.php` file and you will have to redo this step. This is the reason we don't just replace the contents of `index-wp-redis.php` with `index.php`. 40 | 41 | We do this because Wordpress is no longer in charge of displaying our posts. Redis will now serve the post if it is in the cache. If the post is not in the Redis cache it will then call Wordpress to serve the page and then cache it for the next pageload. 42 | 43 | 44 | ### Benchmark 45 | ------ 46 | I welcome you to compare the page load times of this caching system with other popular Caching plugins such as [Wp Super Cache](http://wordpress.org/plugins/wp-super-cache/) and [W3 Total Cache](http://wordpress.org/plugins/w3-total-cache/). 47 | 48 | With a fresh Wordpress install: 49 | 50 | Wp Super Cache 51 | ``` 52 | Page generated in 0.318 seconds. 53 | ``` 54 | 55 | W3 Total Cache 56 | ``` 57 | Page generated in 0.30484 seconds. 58 | ``` 59 | 60 | Wp Redis Cache 61 | ``` 62 | Page generated in 0.00902 seconds. 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /index-wp-redis.php: -------------------------------------------------------------------------------- 1 | \n"; 65 | } 66 | $redis = new Redis(); 67 | 68 | // Sockets can be used as well. Documentation @ https://github.com/nicolasff/phpredis/#connection 69 | $redis->connect($redis_server); 70 | 71 | } else { // Fallback to predis5.2.php 72 | 73 | if ($debug) { 74 | echo "\n"; 75 | } 76 | include_once(dirname($wp_blog_header_path)."/wp-content/plugins/wp-redis-cache/predis5.2.php"); //we need this to use Redis inside of PHP 77 | 78 | // try the client first 79 | try { 80 | if ($sockets) { 81 | $redis = new Predis_Client(array( 82 | 'scheme' => 'unix', 83 | 'path' => $redis_server 84 | )); 85 | } else { 86 | $redis = new Predis_Client(); 87 | } 88 | } catch (Predis_ClientException $e) { // catch predis-thrown exception 89 | die("Predis not found on your server or was unable to run. Error message: " . $e->getMessage()); 90 | } catch (Exception $e) { // catch other exceptions 91 | die("Error occurred. Error message: " . $e->getMessage()); 92 | } 93 | } 94 | 95 | //Either manual refresh cache by adding ?refresh=secret_string after the URL or somebody posting a comment 96 | if (refreshHasSecret($secret_string) || requestHasSecret($secret_string) || isRemotePageLoad($current_url, $websiteIp)) { 97 | if ($debug) { 98 | echo "\n"; 99 | } 100 | $redis->del($redis_key); 101 | $redis->del("ssl_".$redis_key); 102 | require( $wp_blog_header_path ); 103 | 104 | $unlimited = get_option('wp-redis-cache-debug',false); 105 | $seconds_cache_redis = get_option('wp-redis-cache-seconds',43200); 106 | // This page is cached, lets display it 107 | } else if ($redis->exists($redis_key)) { 108 | if ($debug) { 109 | echo "\n"; 110 | } 111 | $cache = true; 112 | $html_of_page = $redis->get($redis_key); 113 | echo $html_of_page; 114 | 115 | // If the cache does not exist lets display the user the normal page without cache, and then fetch a new cache page 116 | } else if ($_SERVER['REMOTE_ADDR'] != $websiteIp && strstr($current_url, 'preview=true') == false) { 117 | if ($debug) { 118 | echo "\n"; 119 | } 120 | 121 | $isPOST = ($_SERVER['REQUEST_METHOD'] === 'POST') ? 1 : 0; 122 | 123 | $loggedIn = preg_match("/wordpress_logged_in/", var_export($_COOKIE, true)); 124 | if (!$isPOST && !$loggedIn) { 125 | ob_start(); 126 | $level = ob_get_level(); 127 | require( $wp_blog_header_path ); 128 | while(ob_get_level() > $level) ob_end_flush(); 129 | $html_of_page = ob_get_clean(); // ob_get_clean also closes the OB 130 | echo $html_of_page; 131 | 132 | if (!is_numeric($seconds_cache_redis)) { 133 | $seconds_cache_redis = 43200; 134 | } 135 | 136 | // When a page displays after an "HTTP 404: Not Found" error occurs, do not cache 137 | // When the search was used, do not cache 138 | if ((!is_404()) and (!is_search())) { 139 | if ($unlimited) { 140 | $redis->set($redis_key, $html_of_page); 141 | } else { 142 | $redis->setex($redis_key, $seconds_cache_redis, $html_of_page); 143 | } 144 | 145 | } 146 | } else { //either the user is logged in, or is posting a comment, show them uncached 147 | require( $wp_blog_header_path ); 148 | } 149 | 150 | } else if ($_SERVER['REMOTE_ADDR'] != $websiteIp && strstr($current_url, 'preview=true') == true) { 151 | require( $wp_blog_header_path ); 152 | } 153 | // else { // This is what your server should get if no cache exists //deprecated, as the ob_start() is cleaner 154 | //require( $wp_blog_header_path ); 155 | // } 156 | } catch (Exception $e) { 157 | //require( $wp_blog_header_path ); 158 | http_response_code(500); 159 | echo "Something went wrong: " . $e->getMessage(); 160 | } 161 | 162 | if ($debug) { 163 | $end = microtime(); 164 | $time = (@getMicroTime($end) - @getMicroTime($start)); 165 | 166 | echo "\n"; 167 | echo "\n"; 168 | if (isset($seconds_cache_redis)) { 169 | echo "\n"; 170 | } 171 | echo "\n"; 172 | echo "\n"; 173 | if (isset($unlimited)) { 174 | echo "\n"; 175 | } 176 | echo "\n"; 177 | } 178 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | ID ); 13 | 14 | // aaronstpierre: this needs to be include_once so as not to cauase a redeclaration error 15 | include_once("predis5.2.php"); //we need this to use Redis inside of PHP 16 | $redis = new Predis_Client(); 17 | 18 | $redis_key = md5($permalink); 19 | $redis->del($redis_key); 20 | $redis->del("ssl_".$redis_key); 21 | 22 | //refresh the front page 23 | $frontPage = get_home_url() . "/"; 24 | $redis_key = md5($frontPage); 25 | $redis->del($redis_key); 26 | $redis->del("ssl_".$redis_key); 27 | } 28 | } 29 | 30 | // clears the whole cache 31 | function clear_wp_redis_cache() 32 | { 33 | include_once("predis5.2.php"); //we need this to use Redis inside of PHP 34 | $args = array( 'post_type' => 'any', 'posts_per_page' => -1); 35 | $wp_query = new WP_Query( $args); // to get all Posts 36 | $redis = new Predis_Client(); 37 | 38 | // Loop all posts and clear the cache 39 | $i = 0; 40 | while ( $wp_query->have_posts() ) : $wp_query->the_post(); 41 | $incremented = false; 42 | $permalink = get_permalink(); 43 | 44 | $redis_key = md5($permalink); 45 | $redis_ssl_key = 'ssl_'.$redis_key; 46 | 47 | if ( ( $redis->exists( $redis_key ) ) == true ) { 48 | $redis->del( $redis_key ); 49 | $i++; 50 | $incremented = true; 51 | } 52 | if ( ( $redis->exists( $redis_ssl_key ) ) == true ) { 53 | $redis->del( $redis_ssl_key ); 54 | if ( !$incremented ) { 55 | $i++; 56 | } 57 | } 58 | 59 | 60 | endwhile; 61 | 62 | echo $i . " of " . $wp_query -> found_posts . " posts was cleared in cache"; 63 | die(); 64 | } 65 | 66 | function clear_wp_redis_cache_javascript() { 67 | ?> 68 | 83 | 37 |
38 |

Wp-Redis Options

39 |
40 | 41 | 42 |

This plugin does not work out of the box and requires additional steps.
43 | Please follow these install instructions: https://github.com/BenjaminAdams/wp-redis-cache

44 | 45 |

If you do not have Redis installed on your machine this will NOT work!

46 | 47 |

Seconds of Caching:
48 | How many seconds would you like to cache? *Recommended 12 hours or 43200 seconds
49 |

50 | 51 |

Cache unlimited:
52 | If this options set the cache never expire. This option overrides the setting "Seconds of Caching"
53 | />

54 | 55 |

56 |

57 | 58 | 59 | 60 |
61 |
62 | getMessage()); 14 | } 15 | } 16 | 17 | // Communication errors 18 | class Predis_CommunicationException extends PredisException { 19 | private $_connection; 20 | 21 | public function __construct(Predis_Connection $connection, $message = null, $code = null) { 22 | $this->_connection = $connection; 23 | parent::__construct($message, $code); 24 | } 25 | 26 | public function getConnection() { return $this->_connection; } 27 | public function shouldResetConnection() { return true; } 28 | } 29 | 30 | // Unexpected responses 31 | class Predis_MalformedServerResponse extends Predis_CommunicationException { } 32 | 33 | /* ------------------------------------------------------------------------- */ 34 | 35 | class Predis_Client { 36 | const VERSION = '0.6.6'; 37 | private $_options, $_connection, $_serverProfile, $_responseReader; 38 | 39 | public function __construct($parameters = null, $clientOptions = null) { 40 | $this->setupClient($clientOptions !== null ? $clientOptions : new Predis_ClientOptions()); 41 | $this->setupConnection($parameters); 42 | } 43 | 44 | public static function create(/* arguments */) { 45 | $argv = func_get_args(); 46 | $argc = func_num_args(); 47 | 48 | $options = null; 49 | $lastArg = $argv[$argc-1]; 50 | if ($argc > 0 && !is_string($lastArg) && ($lastArg instanceof Predis_ClientOptions || 51 | is_subclass_of($lastArg, 'Predis_RedisServerProfile'))) { 52 | $options = array_pop($argv); 53 | $argc--; 54 | } 55 | 56 | if ($argc === 0) { 57 | throw new Predis_ClientException('Missing connection parameters'); 58 | } 59 | 60 | return new Predis_Client($argc === 1 ? $argv[0] : $argv, $options); 61 | } 62 | 63 | private static function filterClientOptions($options) { 64 | if ($options instanceof Predis_ClientOptions) { 65 | return $options; 66 | } 67 | if (is_array($options)) { 68 | return new Predis_ClientOptions($options); 69 | } 70 | if ($options instanceof Predis_RedisServerProfile) { 71 | return new Predis_ClientOptions(array( 72 | 'profile' => $options 73 | )); 74 | } 75 | if (is_string($options)) { 76 | return new Predis_ClientOptions(array( 77 | 'profile' => Predis_RedisServerProfile::get($options) 78 | )); 79 | } 80 | throw new InvalidArgumentException("Invalid type for client options"); 81 | } 82 | 83 | private function setupClient($options) { 84 | $options = self::filterClientOptions($options); 85 | 86 | $this->setProfile($options->profile); 87 | 88 | $reader = $options->reader; 89 | $reader->setOption('iterable_multibulk', $options->iterable_multibulk); 90 | $reader->setOption('throw_on_error', $options->throw_on_error); 91 | 92 | $this->_options = $options; 93 | $this->_responseReader = $reader; 94 | } 95 | 96 | private function setupConnection($parameters) { 97 | if ($parameters !== null && !(is_array($parameters) || is_string($parameters))) { 98 | throw new Predis_ClientException('Invalid parameters type (array or string expected)'); 99 | } 100 | if (is_array($parameters) && isset($parameters[0])) { 101 | $cluster = new Predis_ConnectionCluster($this->_options->key_distribution); 102 | foreach ($parameters as $shardParams) { 103 | $cluster->add($this->createConnection($shardParams)); 104 | } 105 | $this->setConnection($cluster); 106 | } 107 | else { 108 | $this->setConnection($this->createConnection($parameters)); 109 | } 110 | } 111 | 112 | private function createConnection($parameters) { 113 | if (!$parameters instanceof Predis_ConnectionParameters) { 114 | $parameters = new Predis_ConnectionParameters($parameters); 115 | } 116 | 117 | $connection = new Predis_Connection($parameters, $this->_responseReader); 118 | if ($parameters->password !== null) { 119 | $connection->pushInitCommand($this->createCommand( 120 | 'auth', array($parameters->password) 121 | )); 122 | } 123 | if ($parameters->database !== null) { 124 | $connection->pushInitCommand($this->createCommand( 125 | 'select', array($parameters->database) 126 | )); 127 | } 128 | 129 | return $connection; 130 | } 131 | 132 | private function setConnection(Predis_IConnection $connection) { 133 | $this->_connection = $connection; 134 | } 135 | 136 | public function setProfile($serverProfile) { 137 | if ($serverProfile instanceof Predis_RedisServerProfile) { 138 | $this->_serverProfile = $serverProfile; 139 | } 140 | else if (is_string($serverProfile)) { 141 | $this->_serverProfile = Predis_RedisServerProfile::get($serverProfile); 142 | } 143 | else { 144 | throw new InvalidArgumentException( 145 | "Invalid type for server profile, Predis_RedisServerProfile or string expected" 146 | ); 147 | } 148 | } 149 | 150 | public function getProfile() { 151 | return $this->_serverProfile; 152 | } 153 | 154 | public function getResponseReader() { 155 | return $this->_responseReader; 156 | } 157 | 158 | public function getClientFor($connectionAlias) { 159 | if (!Predis_Shared_Utils::isCluster($this->_connection)) { 160 | throw new Predis_ClientException( 161 | 'This method is supported only when the client is connected to a cluster of connections' 162 | ); 163 | } 164 | 165 | $connection = $this->_connection->getConnectionById($connectionAlias); 166 | if ($connection === null) { 167 | throw new InvalidArgumentException( 168 | "Invalid connection alias: '$connectionAlias'" 169 | ); 170 | } 171 | 172 | $newClient = new Predis_Client(); 173 | $newClient->setupClient($this->_options); 174 | $newClient->setConnection($connection); 175 | return $newClient; 176 | } 177 | 178 | public function connect() { 179 | $this->_connection->connect(); 180 | } 181 | 182 | public function disconnect() { 183 | $this->_connection->disconnect(); 184 | } 185 | 186 | public function isConnected() { 187 | return $this->_connection->isConnected(); 188 | } 189 | 190 | public function getConnection($id = null) { 191 | if (!isset($id)) { 192 | return $this->_connection; 193 | } 194 | else { 195 | return Predis_Shared_Utils::isCluster($this->_connection) 196 | ? $this->_connection->getConnectionById($id) 197 | : $this->_connection; 198 | } 199 | } 200 | 201 | public function __call($method, $arguments) { 202 | $command = $this->_serverProfile->createCommand($method, $arguments); 203 | return $this->_connection->executeCommand($command); 204 | } 205 | 206 | public function createCommand($method, $arguments = array()) { 207 | return $this->_serverProfile->createCommand($method, $arguments); 208 | } 209 | 210 | public function executeCommand(Predis_Command $command) { 211 | return $this->_connection->executeCommand($command); 212 | } 213 | 214 | public function executeCommandOnShards(Predis_Command $command) { 215 | $replies = array(); 216 | if (Predis_Shared_Utils::isCluster($this->_connection)) { 217 | foreach($this->_connection as $connection) { 218 | $replies[] = $connection->executeCommand($command); 219 | } 220 | } 221 | else { 222 | $replies[] = $this->_connection->executeCommand($command); 223 | } 224 | return $replies; 225 | } 226 | 227 | public function rawCommand($rawCommandData, $closesConnection = false) { 228 | if (Predis_Shared_Utils::isCluster($this->_connection)) { 229 | throw new Predis_ClientException('Cannot send raw commands when connected to a cluster of Redis servers'); 230 | } 231 | return $this->_connection->rawCommand($rawCommandData, $closesConnection); 232 | } 233 | 234 | private function sharedInitializer($argv, $initializer) { 235 | $argc = count($argv); 236 | if ($argc === 0) { 237 | return $this->$initializer(); 238 | } 239 | else if ($argc === 1) { 240 | list($arg0) = $argv; 241 | return is_array($arg0) ? $this->$initializer($arg0) : $this->$initializer(null, $arg0); 242 | } 243 | else if ($argc === 2) { 244 | list($arg0, $arg1) = $argv; 245 | return $this->$initializer($arg0, $arg1); 246 | } 247 | return $this->$initializer($this, $arguments); 248 | } 249 | 250 | public function pipeline(/* arguments */) { 251 | $args = func_get_args(); 252 | return $this->sharedInitializer($args, 'initPipeline'); 253 | } 254 | 255 | public function pipelineSafe($pipelineBlock = null) { 256 | return $this->initPipeline(array('safe' => true), $pipelineBlock); 257 | } 258 | 259 | private function initPipeline(Array $options = null, $pipelineBlock = null) { 260 | $pipeline = null; 261 | if (isset($options)) { 262 | if (isset($options['safe']) && $options['safe'] == true) { 263 | $connection = $this->getConnection(); 264 | $pipeline = new Predis_CommandPipeline($this, $connection instanceof Predis_Connection 265 | ? new Predis_Pipeline_SafeExecutor($connection) 266 | : new Predis_Pipeline_SafeClusterExecutor($connection) 267 | ); 268 | } 269 | else { 270 | $pipeline = new Predis_CommandPipeline($this); 271 | } 272 | } 273 | else { 274 | $pipeline = new Predis_CommandPipeline($this); 275 | } 276 | return $this->pipelineExecute($pipeline, $pipelineBlock); 277 | } 278 | 279 | private function pipelineExecute(Predis_CommandPipeline $pipeline, $block) { 280 | return $block !== null ? $pipeline->execute($block) : $pipeline; 281 | } 282 | 283 | public function multiExec(/* arguments */) { 284 | $args = func_get_args(); 285 | return $this->sharedInitializer($args, 'initMultiExec'); 286 | } 287 | 288 | private function initMultiExec(Array $options = null, $transBlock = null) { 289 | $multi = isset($options) ? new Predis_MultiExecBlock($this, $options) : new Predis_MultiExecBlock($this); 290 | return $transBlock !== null ? $multi->execute($transBlock) : $multi; 291 | } 292 | 293 | public function pubSubContext(Array $options = null) { 294 | return new Predis_PubSubContext($this, $options); 295 | } 296 | } 297 | 298 | /* ------------------------------------------------------------------------- */ 299 | 300 | interface Predis_IClientOptionsHandler { 301 | public function validate($option, $value); 302 | public function getDefault(); 303 | } 304 | 305 | class Predis_ClientOptionsProfile implements Predis_IClientOptionsHandler { 306 | public function validate($option, $value) { 307 | if ($value instanceof Predis_RedisServerProfile) { 308 | return $value; 309 | } 310 | if (is_string($value)) { 311 | return Predis_RedisServerProfile::get($value); 312 | } 313 | throw new InvalidArgumentException("Invalid value for option $option"); 314 | } 315 | 316 | public function getDefault() { 317 | return Predis_RedisServerProfile::getDefault(); 318 | } 319 | } 320 | 321 | class Predis_ClientOptionsKeyDistribution implements Predis_IClientOptionsHandler { 322 | public function validate($option, $value) { 323 | if ($value instanceof Predis_Distribution_IDistributionStrategy) { 324 | return $value; 325 | } 326 | if (is_string($value)) { 327 | $valueReflection = new ReflectionClass($value); 328 | if ($valueReflection->isSubclassOf('Predis_Distribution_IDistributionStrategy')) { 329 | return new $value; 330 | } 331 | } 332 | throw new InvalidArgumentException("Invalid value for option $option"); 333 | } 334 | 335 | public function getDefault() { 336 | return new Predis_Distribution_HashRing(); 337 | } 338 | } 339 | 340 | class Predis_ClientOptionsIterableMultiBulk implements Predis_IClientOptionsHandler { 341 | public function validate($option, $value) { 342 | return (bool) $value; 343 | } 344 | 345 | public function getDefault() { 346 | return false; 347 | } 348 | } 349 | 350 | class Predis_ClientOptionsThrowOnError implements Predis_IClientOptionsHandler { 351 | public function validate($option, $value) { 352 | return (bool) $value; 353 | } 354 | 355 | public function getDefault() { 356 | return true; 357 | } 358 | } 359 | 360 | class Predis_ClientOptionsReader implements Predis_IClientOptionsHandler { 361 | public function validate($option, $value) { 362 | if ($value instanceof Predis_IResponseReader) { 363 | return $value; 364 | } 365 | if (is_string($value)) { 366 | if ($value === 'composable') { 367 | return new Predis_ResponseReader(); 368 | } 369 | $valueReflection = new ReflectionClass($value); 370 | if ($valueReflection->isSubclassOf('Predis_IResponseReader')) { 371 | return new $value; 372 | } 373 | } 374 | throw new InvalidArgumentException("Invalid value for option $option"); 375 | } 376 | 377 | public function getDefault() { 378 | return new Predis_FastResponseReader(); 379 | } 380 | } 381 | 382 | class Predis_ClientOptions { 383 | private static $_optionsHandlers; 384 | private $_options; 385 | 386 | public function __construct($options = null) { 387 | self::initializeOptionsHandlers(); 388 | $this->initializeOptions($options !== null ? $options : array()); 389 | } 390 | 391 | private static function initializeOptionsHandlers() { 392 | if (!isset(self::$_optionsHandlers)) { 393 | self::$_optionsHandlers = self::getOptionsHandlers(); 394 | } 395 | } 396 | 397 | private static function getOptionsHandlers() { 398 | return array( 399 | 'profile' => new Predis_ClientOptionsProfile(), 400 | 'key_distribution' => new Predis_ClientOptionsKeyDistribution(), 401 | 'iterable_multibulk' => new Predis_ClientOptionsIterableMultiBulk(), 402 | 'throw_on_error' => new Predis_ClientOptionsThrowOnError(), 403 | 'reader' => new Predis_ClientOptionsReader(), 404 | ); 405 | } 406 | 407 | private function initializeOptions($options) { 408 | foreach ($options as $option => $value) { 409 | if (isset(self::$_optionsHandlers[$option])) { 410 | $handler = self::$_optionsHandlers[$option]; 411 | $this->_options[$option] = $handler->validate($option, $value); 412 | } 413 | } 414 | } 415 | 416 | public function __get($option) { 417 | if (!isset($this->_options[$option])) { 418 | $defaultValue = self::$_optionsHandlers[$option]->getDefault(); 419 | $this->_options[$option] = $defaultValue; 420 | } 421 | return $this->_options[$option]; 422 | } 423 | 424 | public function __isset($option) { 425 | return isset(self::$_optionsHandlers[$option]); 426 | } 427 | } 428 | 429 | /* ------------------------------------------------------------------------- */ 430 | 431 | class Predis_Protocol { 432 | const NEWLINE = "\r\n"; 433 | const OK = 'OK'; 434 | const ERROR = 'ERR'; 435 | const QUEUED = 'QUEUED'; 436 | const NULL = 'nil'; 437 | 438 | const PREFIX_STATUS = '+'; 439 | const PREFIX_ERROR = '-'; 440 | const PREFIX_INTEGER = ':'; 441 | const PREFIX_BULK = '$'; 442 | const PREFIX_MULTI_BULK = '*'; 443 | } 444 | 445 | abstract class Predis_Command { 446 | private $_hash; 447 | private $_arguments = array(); 448 | 449 | public abstract function getCommandId(); 450 | 451 | public abstract function serializeRequest($command, $arguments); 452 | 453 | public function canBeHashed() { 454 | return true; 455 | } 456 | 457 | public function getHash(Predis_Distribution_IDistributionStrategy $distributor) { 458 | if (isset($this->_hash)) { 459 | return $this->_hash; 460 | } 461 | 462 | if (isset($this->_arguments[0])) { 463 | // TODO: should we throw an exception if the command does 464 | // not support sharding? 465 | $key = $this->_arguments[0]; 466 | 467 | $start = strpos($key, '{'); 468 | if ($start !== false) { 469 | $end = strpos($key, '}', $start); 470 | if ($end !== false) { 471 | $key = substr($key, ++$start, $end - $start); 472 | } 473 | } 474 | 475 | $this->_hash = $distributor->generateKey($key); 476 | return $this->_hash; 477 | } 478 | 479 | return null; 480 | } 481 | 482 | public function closesConnection() { 483 | return false; 484 | } 485 | 486 | protected function filterArguments(Array $arguments) { 487 | return $arguments; 488 | } 489 | 490 | public function setArguments(/* arguments */) { 491 | $this->_arguments = $this->filterArguments(func_get_args()); 492 | unset($this->_hash); 493 | } 494 | 495 | public function setArgumentsArray(Array $arguments) { 496 | $this->_arguments = $this->filterArguments($arguments); 497 | unset($this->_hash); 498 | } 499 | 500 | public function getArguments() { 501 | return $this->_arguments; 502 | } 503 | 504 | public function getArgument($index = 0) { 505 | if (isset($this->_arguments[$index]) === true) { 506 | return $this->_arguments[$index]; 507 | } 508 | } 509 | 510 | public function parseResponse($data) { 511 | return $data; 512 | } 513 | 514 | public final function invoke() { 515 | return $this->serializeRequest($this->getCommandId(), $this->getArguments()); 516 | } 517 | } 518 | 519 | abstract class Predis_InlineCommand extends Predis_Command { 520 | public function serializeRequest($command, $arguments) { 521 | if (isset($arguments[0]) && is_array($arguments[0])) { 522 | $arguments[0] = implode($arguments[0], ' '); 523 | } 524 | return $command . (count($arguments) > 0 525 | ? ' ' . implode($arguments, ' ') . "\r\n" : "\r\n" 526 | ); 527 | } 528 | } 529 | 530 | abstract class Predis_BulkCommand extends Predis_Command { 531 | public function serializeRequest($command, $arguments) { 532 | $data = array_pop($arguments); 533 | if (is_array($data)) { 534 | $data = implode($data, ' '); 535 | } 536 | return $command . ' ' . implode($arguments, ' ') . ' ' . strlen($data) . 537 | "\r\n" . $data . "\r\n"; 538 | } 539 | } 540 | 541 | abstract class Predis_MultiBulkCommand extends Predis_Command { 542 | public function serializeRequest($command, $arguments) { 543 | $cmd_args = null; 544 | $argsc = count($arguments); 545 | 546 | if ($argsc === 1 && is_array($arguments[0])) { 547 | $cmd_args = $arguments[0]; 548 | $argsc = count($cmd_args); 549 | } 550 | else { 551 | $cmd_args = $arguments; 552 | } 553 | 554 | $cmdlen = strlen($command); 555 | $reqlen = $argsc + 1; 556 | 557 | $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$command}\r\n"; 558 | for ($i = 0; $i < $reqlen - 1; $i++) { 559 | $argument = $cmd_args[$i]; 560 | $arglen = strlen($argument); 561 | $buffer .= "\${$arglen}\r\n{$argument}\r\n"; 562 | } 563 | 564 | return $buffer; 565 | } 566 | } 567 | 568 | /* ------------------------------------------------------------------------- */ 569 | 570 | interface Predis_IResponseHandler { 571 | function handle(Predis_Connection $connection, $payload); 572 | } 573 | 574 | class Predis_ResponseStatusHandler implements Predis_IResponseHandler { 575 | public function handle(Predis_Connection $connection, $status) { 576 | if ($status === "OK") { 577 | return true; 578 | } 579 | if ($status === "QUEUED") { 580 | return new Predis_ResponseQueued(); 581 | } 582 | return $status; 583 | } 584 | } 585 | 586 | class Predis_ResponseErrorHandler implements Predis_IResponseHandler { 587 | public function handle(Predis_Connection $connection, $errorMessage) { 588 | throw new Predis_ServerException(substr($errorMessage, 4)); 589 | } 590 | } 591 | 592 | class Predis_ResponseErrorSilentHandler implements Predis_IResponseHandler { 593 | public function handle(Predis_Connection $connection, $errorMessage) { 594 | return new Predis_ResponseError(substr($errorMessage, 4)); 595 | } 596 | } 597 | 598 | class Predis_ResponseBulkHandler implements Predis_IResponseHandler { 599 | public function handle(Predis_Connection $connection, $lengthString) { 600 | $length = (int) $lengthString; 601 | if ($length != $lengthString) { 602 | Predis_Shared_Utils::onCommunicationException(new Predis_MalformedServerResponse( 603 | $connection, "Cannot parse '$length' as data length" 604 | )); 605 | } 606 | if ($length >= 0) { 607 | return substr($connection->readBytes($length + 2), 0, -2); 608 | } 609 | if ($length == -1) { 610 | return null; 611 | } 612 | } 613 | } 614 | 615 | class Predis_ResponseMultiBulkHandler implements Predis_IResponseHandler { 616 | public function handle(Predis_Connection $connection, $lengthString) { 617 | $listLength = (int) $lengthString; 618 | if ($listLength != $lengthString) { 619 | Predis_Shared_Utils::onCommunicationException(new Predis_MalformedServerResponse( 620 | $connection, "Cannot parse '$lengthString' as data length" 621 | )); 622 | } 623 | 624 | if ($listLength === -1) { 625 | return null; 626 | } 627 | 628 | $list = array(); 629 | 630 | if ($listLength > 0) { 631 | $handlers = array(); 632 | $reader = $connection->getResponseReader(); 633 | for ($i = 0; $i < $listLength; $i++) { 634 | $header = $connection->readLine(); 635 | $prefix = $header[0]; 636 | if (isset($handlers[$prefix])) { 637 | $handler = $handlers[$prefix]; 638 | } 639 | else { 640 | $handler = $reader->getHandler($prefix); 641 | $handlers[$prefix] = $handler; 642 | } 643 | $list[$i] = $handler->handle($connection, substr($header, 1)); 644 | } 645 | } 646 | 647 | return $list; 648 | } 649 | } 650 | 651 | class Predis_ResponseMultiBulkStreamHandler implements Predis_IResponseHandler { 652 | public function handle(Predis_Connection $connection, $lengthString) { 653 | $listLength = (int) $lengthString; 654 | if ($listLength != $lengthString) { 655 | Predis_Shared_Utils::onCommunicationException(new Predis_MalformedServerResponse( 656 | $connection, "Cannot parse '$lengthString' as data length" 657 | )); 658 | } 659 | return new Predis_Shared_MultiBulkResponseIterator($connection, $lengthString); 660 | } 661 | } 662 | 663 | class Predis_ResponseIntegerHandler implements Predis_IResponseHandler { 664 | public function handle(Predis_Connection $connection, $number) { 665 | if (is_numeric($number)) { 666 | return (int) $number; 667 | } 668 | else { 669 | if ($number !== 'nil') { 670 | Predis_Shared_Utils::onCommunicationException(new Predis_MalformedServerResponse( 671 | $connection, "Cannot parse '$number' as numeric response" 672 | )); 673 | } 674 | return null; 675 | } 676 | } 677 | } 678 | 679 | interface Predis_IResponseReader { 680 | public function read(Predis_Connection $connection); 681 | public function setOption($option, $value); 682 | public function getOption($option); 683 | } 684 | 685 | class Predis_FastResponseReader implements Predis_IResponseReader { 686 | private $_iterableMultibulk, $_throwErrors; 687 | 688 | public function __construct() { 689 | $this->_iterableMultibulk = false; 690 | $this->_throwErrors = true; 691 | } 692 | 693 | public function read(Predis_Connection $connection) { 694 | $chunk = $connection->readLine(); 695 | $prefix = $chunk[0]; 696 | $payload = substr($chunk, 1); 697 | switch ($prefix) { 698 | case '+': // inline 699 | switch ($payload) { 700 | case 'OK': 701 | return true; 702 | case 'QUEUED': 703 | return new Predis_ResponseQueued(); 704 | default: 705 | return $payload; 706 | } 707 | 708 | case '$': // bulk 709 | $size = (int) $payload; 710 | if ($size === -1) { 711 | return null; 712 | } 713 | return substr($connection->readBytes($size + 2), 0, -2); 714 | 715 | case '*': // multi bulk 716 | $count = (int) $payload; 717 | if ($count === -1) { 718 | return null; 719 | } 720 | if ($this->_iterableMultibulk) { 721 | return new Predis_Shared_MultiBulkResponseIterator($connection, $count); 722 | } 723 | $multibulk = array(); 724 | for ($i = 0; $i < $count; $i++) { 725 | $multibulk[$i] = $this->read($connection); 726 | } 727 | return $multibulk; 728 | 729 | case ':': // integer 730 | return (int) $payload; 731 | 732 | case '-': // error 733 | $errorMessage = substr($payload, 4); 734 | if ($this->_throwErrors) { 735 | throw new Predis_ServerException($errorMessage); 736 | } 737 | return new Predis_ResponseError($errorMessage); 738 | 739 | default: 740 | throw new Predis_CommunicationException( 741 | $connection, "Unknown prefix: '$prefix'" 742 | ); 743 | } 744 | } 745 | 746 | public function setOption($option, $value) { 747 | switch ($option) { 748 | case 'iterable_multibulk': 749 | $this->_iterableMultibulk = (bool) $value; 750 | break; 751 | case 'throw_on_error': 752 | $this->_throwErrors = (bool) $value; 753 | break; 754 | } 755 | } 756 | 757 | public function getOption($option) { 758 | switch ($option) { 759 | case 'iterable_multibulk': 760 | return $this->_iterableMultibulk; 761 | case 'throw_on_error': 762 | return $this->_throwErrors; 763 | } 764 | } 765 | } 766 | 767 | class Predis_ResponseReader implements Predis_IResponseReader { 768 | private $_prefixHandlers; 769 | 770 | public function __construct() { 771 | $this->initializePrefixHandlers(); 772 | } 773 | 774 | private function initializePrefixHandlers() { 775 | $this->_prefixHandlers = array( 776 | Predis_Protocol::PREFIX_STATUS => new Predis_ResponseStatusHandler(), 777 | Predis_Protocol::PREFIX_ERROR => new Predis_ResponseErrorHandler(), 778 | Predis_Protocol::PREFIX_INTEGER => new Predis_ResponseIntegerHandler(), 779 | Predis_Protocol::PREFIX_BULK => new Predis_ResponseBulkHandler(), 780 | Predis_Protocol::PREFIX_MULTI_BULK => new Predis_ResponseMultiBulkHandler(), 781 | ); 782 | } 783 | 784 | public function setHandler($prefix, Predis_IResponseHandler $handler) { 785 | $this->_prefixHandlers[$prefix] = $handler; 786 | } 787 | 788 | public function getHandler($prefix) { 789 | if (isset($this->_prefixHandlers[$prefix])) { 790 | return $this->_prefixHandlers[$prefix]; 791 | } 792 | } 793 | 794 | public function read(Predis_Connection $connection) { 795 | $header = $connection->readLine(); 796 | if ($header === '') { 797 | $this->throwMalformedResponse($connection, 'Unexpected empty header'); 798 | } 799 | 800 | $prefix = $header[0]; 801 | if (!isset($this->_prefixHandlers[$prefix])) { 802 | $this->throwMalformedResponse($connection, "Unknown prefix '$prefix'"); 803 | } 804 | 805 | $handler = $this->_prefixHandlers[$prefix]; 806 | return $handler->handle($connection, substr($header, 1)); 807 | } 808 | 809 | private function throwMalformedResponse(Predis_Connection $connection, $message) { 810 | Predis_Shared_Utils::onCommunicationException(new Predis_MalformedServerResponse( 811 | $connection, $message 812 | )); 813 | } 814 | 815 | public function setOption($option, $value) { 816 | switch ($option) { 817 | case 'iterable_multibulk': 818 | $handler = $value ? 'Predis_ResponseMultiBulkStreamHandler' : 'Predis_ResponseMultiBulkHandler'; 819 | $this->_prefixHandlers[Predis_Protocol::PREFIX_MULTI_BULK] = new $handler(); 820 | break; 821 | case 'throw_on_error': 822 | $handler = $value ? 'Predis_ResponseErrorHandler' : 'Predis_ResponseErrorSilentHandler'; 823 | $this->_prefixHandlers[Predis_Protocol::PREFIX_ERROR] = new $handler(); 824 | break; 825 | } 826 | } 827 | 828 | public function getOption($option) { 829 | switch ($option) { 830 | case 'iterable_multibulk': 831 | return $this->_prefixHandlers[Predis_Protocol::PREFIX_MULTI_BULK] instanceof Predis_ResponseMultiBulkStreamHandler; 832 | case 'throw_on_error': 833 | return $this->_prefixHandlers[Predis_Protocol::PREFIX_ERROR] instanceof Predis_ResponseErrorHandler; 834 | } 835 | } 836 | } 837 | 838 | class Predis_ResponseError { 839 | public $skipParse = true; 840 | private $_message; 841 | 842 | public function __construct($message) { 843 | $this->_message = $message; 844 | } 845 | 846 | public function __get($property) { 847 | if ($property === 'error') { 848 | return true; 849 | } 850 | if ($property === 'message') { 851 | return $this->_message; 852 | } 853 | } 854 | 855 | public function __isset($property) { 856 | return $property === 'error'; 857 | } 858 | 859 | public function __toString() { 860 | return $this->_message; 861 | } 862 | } 863 | 864 | class Predis_ResponseQueued { 865 | public $skipParse = true; 866 | 867 | public function __toString() { 868 | return Predis_Protocol::QUEUED; 869 | } 870 | 871 | public function __get($property) { 872 | if ($property === 'queued') { 873 | return true; 874 | } 875 | } 876 | 877 | public function __isset($property) { 878 | return $property === 'queued'; 879 | } 880 | } 881 | 882 | /* ------------------------------------------------------------------------- */ 883 | 884 | class Predis_CommandPipeline { 885 | private $_redisClient, $_pipelineBuffer, $_returnValues, $_running, $_executor; 886 | 887 | public function __construct(Predis_Client $redisClient, Predis_Pipeline_IPipelineExecutor $executor = null) { 888 | $this->_redisClient = $redisClient; 889 | $this->_executor = $executor !== null ? $executor : new Predis_Pipeline_StandardExecutor(); 890 | $this->_pipelineBuffer = array(); 891 | $this->_returnValues = array(); 892 | } 893 | 894 | public function __call($method, $arguments) { 895 | $command = $this->_redisClient->createCommand($method, $arguments); 896 | $this->recordCommand($command); 897 | return $this; 898 | } 899 | 900 | private function recordCommand(Predis_Command $command) { 901 | $this->_pipelineBuffer[] = $command; 902 | } 903 | 904 | private function getRecordedCommands() { 905 | return $this->_pipelineBuffer; 906 | } 907 | 908 | public function flushPipeline() { 909 | if (count($this->_pipelineBuffer) > 0) { 910 | $connection = $this->_redisClient->getConnection(); 911 | $this->_returnValues = array_merge( 912 | $this->_returnValues, 913 | $this->_executor->execute($connection, $this->_pipelineBuffer) 914 | ); 915 | $this->_pipelineBuffer = array(); 916 | } 917 | return $this; 918 | } 919 | 920 | private function setRunning($bool) { 921 | if ($bool == true && $this->_running == true) { 922 | throw new Predis_ClientException("This pipeline is already opened"); 923 | } 924 | $this->_running = $bool; 925 | } 926 | 927 | public function execute($block = null) { 928 | if ($block && !is_callable($block)) { 929 | throw new InvalidArgumentException('Argument passed must be a callable object'); 930 | } 931 | 932 | // TODO: do not reuse previously executed pipelines 933 | $this->setRunning(true); 934 | $pipelineBlockException = null; 935 | 936 | try { 937 | if ($block !== null) { 938 | $block($this); 939 | } 940 | $this->flushPipeline(); 941 | } 942 | catch (Exception $exception) { 943 | $pipelineBlockException = $exception; 944 | } 945 | 946 | $this->setRunning(false); 947 | 948 | if ($pipelineBlockException !== null) { 949 | throw $pipelineBlockException; 950 | } 951 | 952 | return $this->_returnValues; 953 | } 954 | } 955 | 956 | class Predis_MultiExecBlock { 957 | private $_initialized, $_discarded, $_insideBlock, $_checkAndSet, $_watchedKeys; 958 | private $_redisClient, $_options, $_commands; 959 | private $_supportsWatch; 960 | 961 | public function __construct(Predis_Client $redisClient, Array $options = null) { 962 | $this->checkCapabilities($redisClient); 963 | $this->_options = isset($options) ? $options : array(); 964 | $this->_redisClient = $redisClient; 965 | $this->reset(); 966 | } 967 | 968 | private function checkCapabilities(Predis_Client $redisClient) { 969 | if (Predis_Shared_Utils::isCluster($redisClient->getConnection())) { 970 | throw new Predis_ClientException( 971 | 'Cannot initialize a MULTI/EXEC context over a cluster of connections' 972 | ); 973 | } 974 | $profile = $redisClient->getProfile(); 975 | if ($profile->supportsCommands(array('multi', 'exec', 'discard')) === false) { 976 | throw new Predis_ClientException( 977 | 'The current profile does not support MULTI, EXEC and DISCARD commands' 978 | ); 979 | } 980 | $this->_supportsWatch = $profile->supportsCommands(array('watch', 'unwatch')); 981 | } 982 | 983 | private function isWatchSupported() { 984 | if ($this->_supportsWatch === false) { 985 | throw new Predis_ClientException( 986 | 'The current profile does not support WATCH and UNWATCH commands' 987 | ); 988 | } 989 | } 990 | 991 | private function reset() { 992 | $this->_initialized = false; 993 | $this->_discarded = false; 994 | $this->_checkAndSet = false; 995 | $this->_insideBlock = false; 996 | $this->_watchedKeys = false; 997 | $this->_commands = array(); 998 | } 999 | 1000 | private function initialize() { 1001 | if ($this->_initialized === true) { 1002 | return; 1003 | } 1004 | $options = &$this->_options; 1005 | $this->_checkAndSet = isset($options['cas']) && $options['cas']; 1006 | if (isset($options['watch'])) { 1007 | $this->watch($options['watch']); 1008 | } 1009 | if (!$this->_checkAndSet || ($this->_discarded && $this->_checkAndSet)) { 1010 | $this->_redisClient->multi(); 1011 | if ($this->_discarded) { 1012 | $this->_checkAndSet = false; 1013 | } 1014 | } 1015 | $this->_initialized = true; 1016 | $this->_discarded = false; 1017 | } 1018 | 1019 | public function __call($method, $arguments) { 1020 | $this->initialize(); 1021 | $client = $this->_redisClient; 1022 | if ($this->_checkAndSet) { 1023 | return call_user_func_array(array($client, $method), $arguments); 1024 | } 1025 | $command = $client->createCommand($method, $arguments); 1026 | $response = $client->executeCommand($command); 1027 | if (!$response instanceof Predis_ResponseQueued) { 1028 | $this->malformedServerResponse( 1029 | 'The server did not respond with a QUEUED status reply' 1030 | ); 1031 | } 1032 | $this->_commands[] = $command; 1033 | return $this; 1034 | } 1035 | 1036 | public function watch($keys) { 1037 | $this->isWatchSupported(); 1038 | if ($this->_initialized && !$this->_checkAndSet) { 1039 | throw new Predis_ClientException('WATCH inside MULTI is not allowed'); 1040 | } 1041 | $this->_watchedKeys = true; 1042 | return $this->_redisClient->watch($keys); 1043 | } 1044 | 1045 | public function multi() { 1046 | if ($this->_initialized && $this->_checkAndSet) { 1047 | $this->_checkAndSet = false; 1048 | $this->_redisClient->multi(); 1049 | return $this; 1050 | } 1051 | $this->initialize(); 1052 | return $this; 1053 | } 1054 | 1055 | public function unwatch() { 1056 | $this->isWatchSupported(); 1057 | $this->_watchedKeys = false; 1058 | $this->_redisClient->unwatch(); 1059 | return $this; 1060 | } 1061 | 1062 | public function discard() { 1063 | if ($this->_initialized === true) { 1064 | $command = $this->_checkAndSet ? 'unwatch' : 'discard'; 1065 | $this->_redisClient->$command(); 1066 | $this->reset(); 1067 | $this->_discarded = true; 1068 | } 1069 | return $this; 1070 | } 1071 | 1072 | public function exec() { 1073 | return $this->execute(); 1074 | } 1075 | 1076 | private function checkBeforeExecution($block) { 1077 | if ($this->_insideBlock === true) { 1078 | throw new Predis_ClientException( 1079 | "Cannot invoke 'execute' or 'exec' inside an active client transaction block" 1080 | ); 1081 | } 1082 | if ($block) { 1083 | if (!is_callable($block)) { 1084 | throw new InvalidArgumentException( 1085 | 'Argument passed must be a callable object' 1086 | ); 1087 | } 1088 | if (count($this->_commands) > 0) { 1089 | $this->discard(); 1090 | throw new Predis_ClientException( 1091 | 'Cannot execute a transaction block after using fluent interface' 1092 | ); 1093 | } 1094 | } 1095 | if (isset($this->_options['retry']) && !isset($block)) { 1096 | $this->discard(); 1097 | throw new InvalidArgumentException( 1098 | 'Automatic retries can be used only when a transaction block is provided' 1099 | ); 1100 | } 1101 | } 1102 | 1103 | public function execute($block = null) { 1104 | $this->checkBeforeExecution($block); 1105 | 1106 | $reply = null; 1107 | $returnValues = array(); 1108 | $attemptsLeft = isset($this->_options['retry']) ? (int)$this->_options['retry'] : 0; 1109 | do { 1110 | $blockException = null; 1111 | if ($block !== null) { 1112 | $this->_insideBlock = true; 1113 | try { 1114 | $block($this); 1115 | } 1116 | catch (Predis_CommunicationException $exception) { 1117 | $blockException = $exception; 1118 | } 1119 | catch (Predis_ServerException $exception) { 1120 | $blockException = $exception; 1121 | } 1122 | catch (Exception $exception) { 1123 | $blockException = $exception; 1124 | $this->discard(); 1125 | } 1126 | $this->_insideBlock = false; 1127 | if ($blockException !== null) { 1128 | throw $blockException; 1129 | } 1130 | } 1131 | 1132 | if (count($this->_commands) === 0) { 1133 | if ($this->_watchedKeys) { 1134 | $this->discard(); 1135 | return; 1136 | } 1137 | return; 1138 | } 1139 | 1140 | $reply = $this->_redisClient->exec(); 1141 | if ($reply === null) { 1142 | if ($attemptsLeft === 0) { 1143 | throw new Predis_AbortedMultiExec( 1144 | 'The current transaction has been aborted by the server' 1145 | ); 1146 | } 1147 | $this->reset(); 1148 | if (isset($this->_options['on_retry']) && is_callable($this->_options['on_retry'])) { 1149 | call_user_func($this->_options['on_retry'], $this, $attemptsLeft); 1150 | } 1151 | continue; 1152 | } 1153 | break; 1154 | } while ($attemptsLeft-- > 0); 1155 | 1156 | $execReply = $reply instanceof Iterator ? iterator_to_array($reply) : $reply; 1157 | $sizeofReplies = count($execReply); 1158 | 1159 | $commands = &$this->_commands; 1160 | if ($sizeofReplies !== count($commands)) { 1161 | $this->malformedServerResponse( 1162 | 'Unexpected number of responses for a MultiExecBlock' 1163 | ); 1164 | } 1165 | for ($i = 0; $i < $sizeofReplies; $i++) { 1166 | $returnValues[] = $commands[$i]->parseResponse($execReply[$i] instanceof Iterator 1167 | ? iterator_to_array($execReply[$i]) 1168 | : $execReply[$i] 1169 | ); 1170 | unset($commands[$i]); 1171 | } 1172 | 1173 | return $returnValues; 1174 | } 1175 | 1176 | private function malformedServerResponse($message) { 1177 | // Since a MULTI/EXEC block cannot be initialized over a clustered 1178 | // connection, we can safely assume that Predis_Client::getConnection() 1179 | // will always return an instance of Predis_Connection. 1180 | Predis_Shared_Utils::onCommunicationException( 1181 | new Predis_MalformedServerResponse( 1182 | $this->_redisClient->getConnection(), $message 1183 | ) 1184 | ); 1185 | } 1186 | } 1187 | 1188 | class Predis_PubSubContext implements Iterator { 1189 | const SUBSCRIBE = 'subscribe'; 1190 | const UNSUBSCRIBE = 'unsubscribe'; 1191 | const PSUBSCRIBE = 'psubscribe'; 1192 | const PUNSUBSCRIBE = 'punsubscribe'; 1193 | const MESSAGE = 'message'; 1194 | const PMESSAGE = 'pmessage'; 1195 | 1196 | const STATUS_VALID = 0x0001; 1197 | const STATUS_SUBSCRIBED = 0x0010; 1198 | const STATUS_PSUBSCRIBED = 0x0100; 1199 | 1200 | private $_redisClient, $_position, $_options; 1201 | 1202 | public function __construct(Predis_Client $redisClient, Array $options = null) { 1203 | $this->checkCapabilities($redisClient); 1204 | $this->_options = isset($options) ? $options : array(); 1205 | $this->_redisClient = $redisClient; 1206 | $this->_statusFlags = self::STATUS_VALID; 1207 | 1208 | $this->genericSubscribeInit('subscribe'); 1209 | $this->genericSubscribeInit('psubscribe'); 1210 | } 1211 | 1212 | public function __destruct() { 1213 | if ($this->valid()) { 1214 | $this->closeContext(); 1215 | } 1216 | } 1217 | 1218 | private function checkCapabilities(Predis_Client $redisClient) { 1219 | if (Predis_Shared_Utils::isCluster($redisClient->getConnection())) { 1220 | throw new Predis_ClientException( 1221 | 'Cannot initialize a PUB/SUB context over a cluster of connections' 1222 | ); 1223 | } 1224 | $profile = $redisClient->getProfile(); 1225 | $commands = array('publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe'); 1226 | if ($profile->supportsCommands($commands) === false) { 1227 | throw new Predis_ClientException( 1228 | 'The current profile does not support PUB/SUB related commands' 1229 | ); 1230 | } 1231 | } 1232 | 1233 | private function genericSubscribeInit($subscribeAction) { 1234 | if (isset($this->_options[$subscribeAction])) { 1235 | $this->$subscribeAction($this->_options[$subscribeAction]); 1236 | } 1237 | } 1238 | 1239 | private function isFlagSet($value) { 1240 | return ($this->_statusFlags & $value) === $value; 1241 | } 1242 | 1243 | public function subscribe(/* arguments */) { 1244 | $args = func_get_args(); 1245 | $this->writeCommand(self::SUBSCRIBE, $args); 1246 | $this->_statusFlags |= self::STATUS_SUBSCRIBED; 1247 | } 1248 | 1249 | public function unsubscribe(/* arguments */) { 1250 | $args = func_get_args(); 1251 | $this->writeCommand(self::UNSUBSCRIBE, $args); 1252 | } 1253 | 1254 | public function psubscribe(/* arguments */) { 1255 | $args = func_get_args(); 1256 | $this->writeCommand(self::PSUBSCRIBE, $args); 1257 | $this->_statusFlags |= self::STATUS_PSUBSCRIBED; 1258 | } 1259 | 1260 | public function punsubscribe(/* arguments */) { 1261 | $args = func_get_args(); 1262 | $this->writeCommand(self::PUNSUBSCRIBE, $args); 1263 | } 1264 | 1265 | public function closeContext() { 1266 | if ($this->valid()) { 1267 | if ($this->isFlagSet(self::STATUS_SUBSCRIBED)) { 1268 | $this->unsubscribe(); 1269 | } 1270 | if ($this->isFlagSet(self::STATUS_PSUBSCRIBED)) { 1271 | $this->punsubscribe(); 1272 | } 1273 | } 1274 | } 1275 | 1276 | private function writeCommand($method, $arguments) { 1277 | if (count($arguments) === 1 && is_array($arguments[0])) { 1278 | $arguments = $arguments[0]; 1279 | } 1280 | $command = $this->_redisClient->createCommand($method, $arguments); 1281 | $this->_redisClient->getConnection()->writeCommand($command); 1282 | } 1283 | 1284 | public function rewind() { 1285 | // NOOP 1286 | } 1287 | 1288 | public function current() { 1289 | return $this->getValue(); 1290 | } 1291 | 1292 | public function key() { 1293 | return $this->_position; 1294 | } 1295 | 1296 | public function next() { 1297 | if ($this->isFlagSet(self::STATUS_VALID)) { 1298 | $this->_position++; 1299 | } 1300 | return $this->_position; 1301 | } 1302 | 1303 | public function valid() { 1304 | $subscriptions = self::STATUS_SUBSCRIBED + self::STATUS_PSUBSCRIBED; 1305 | return $this->isFlagSet(self::STATUS_VALID) 1306 | && ($this->_statusFlags & $subscriptions) > 0; 1307 | } 1308 | 1309 | private function invalidate() { 1310 | $this->_statusFlags = 0x0000; 1311 | } 1312 | 1313 | private function getValue() { 1314 | $reader = $this->_redisClient->getResponseReader(); 1315 | $connection = $this->_redisClient->getConnection(); 1316 | $response = $reader->read($connection); 1317 | 1318 | switch ($response[0]) { 1319 | case self::SUBSCRIBE: 1320 | case self::UNSUBSCRIBE: 1321 | case self::PSUBSCRIBE: 1322 | case self::PUNSUBSCRIBE: 1323 | if ($response[2] === 0) { 1324 | $this->invalidate(); 1325 | } 1326 | case self::MESSAGE: 1327 | return (object) array( 1328 | 'kind' => $response[0], 1329 | 'channel' => $response[1], 1330 | 'payload' => $response[2], 1331 | ); 1332 | case self::PMESSAGE: 1333 | return (object) array( 1334 | 'kind' => $response[0], 1335 | 'pattern' => $response[1], 1336 | 'channel' => $response[2], 1337 | 'payload' => $response[3], 1338 | ); 1339 | default: 1340 | throw new Predis_ClientException( 1341 | "Received an unknown message type {$response[0]} inside of a pubsub context" 1342 | ); 1343 | } 1344 | } 1345 | } 1346 | 1347 | /* ------------------------------------------------------------------------- */ 1348 | 1349 | class Predis_ConnectionParameters { 1350 | const DEFAULT_SCHEME = 'redis'; 1351 | const DEFAULT_HOST = '127.0.0.1'; 1352 | const DEFAULT_PORT = 6379; 1353 | const DEFAULT_TIMEOUT = 5; 1354 | 1355 | private static $_defaultParameters = array( 1356 | 'scheme' => self::DEFAULT_SCHEME, 1357 | 'host' => self::DEFAULT_HOST, 1358 | 'port' => self::DEFAULT_PORT, 1359 | 'database' => null, 1360 | 'password' => null, 1361 | 'connection_async' => false, 1362 | 'connection_persistent' => false, 1363 | 'connection_timeout' => self::DEFAULT_TIMEOUT, 1364 | 'read_write_timeout' => null, 1365 | 'alias' => null, 1366 | 'weight' => null, 1367 | 'path' => null, 1368 | ); 1369 | 1370 | private $_parameters; 1371 | 1372 | public function __construct($parameters = null) { 1373 | $parameters = $parameters !== null ? $parameters : array(); 1374 | $extractor = is_array($parameters) ? 'filter' : 'parseURI'; 1375 | $this->_parameters = $this->$extractor($parameters); 1376 | } 1377 | 1378 | private function parseURI($uri) { 1379 | if (stripos($uri, 'unix') === 0) { 1380 | // Hack to support URIs for UNIX sockets with minimal effort. 1381 | $uri = str_ireplace('unix:///', 'unix://localhost/', $uri); 1382 | } 1383 | if (($parsed = @parse_url($uri)) === false || !isset($parsed['host'])) { 1384 | throw new Predis_ClientException("Invalid URI: $uri"); 1385 | } 1386 | if (isset($parsed['query'])) { 1387 | foreach (explode('&', $parsed['query']) as $kv) { 1388 | @list($k, $v) = explode('=', $kv); 1389 | $parsed[$k] = $v; 1390 | } 1391 | unset($parsed['query']); 1392 | } 1393 | return $this->filter($parsed); 1394 | } 1395 | 1396 | private function filter($parameters) { 1397 | return array_merge(self::$_defaultParameters, $parameters); 1398 | } 1399 | 1400 | public function __get($parameter) { 1401 | return $this->_parameters[$parameter]; 1402 | } 1403 | 1404 | public function __isset($parameter) { 1405 | return isset($this->_parameters[$parameter]); 1406 | } 1407 | } 1408 | 1409 | interface Predis_IConnection { 1410 | public function connect(); 1411 | public function disconnect(); 1412 | public function isConnected(); 1413 | public function writeCommand(Predis_Command $command); 1414 | public function readResponse(Predis_Command $command); 1415 | public function executeCommand(Predis_Command $command); 1416 | } 1417 | 1418 | class Predis_Connection implements Predis_IConnection { 1419 | private static $_allowedSchemes = array('redis', 'tcp', 'unix'); 1420 | private $_params, $_socket, $_initCmds, $_reader; 1421 | 1422 | public function __construct(Predis_ConnectionParameters $parameters, Predis_IResponseReader $reader = null) { 1423 | if (!in_array($parameters->scheme, self::$_allowedSchemes)) { 1424 | throw new InvalidArgumentException("Invalid scheme: {$parameters->scheme}"); 1425 | } 1426 | $this->_params = $parameters; 1427 | $this->_initCmds = array(); 1428 | $this->_reader = $reader !== null ? $reader : new Predis_FastResponseReader(); 1429 | } 1430 | 1431 | public function __destruct() { 1432 | if (!$this->_params->connection_persistent) { 1433 | $this->disconnect(); 1434 | } 1435 | } 1436 | 1437 | public function isConnected() { 1438 | return isset($this->_socket); 1439 | } 1440 | 1441 | public function connect() { 1442 | if ($this->isConnected()) { 1443 | throw new Predis_ClientException('Connection already estabilished'); 1444 | } 1445 | $initializer = "{$this->_params->scheme}StreamInitializer"; 1446 | $this->_socket = $this->$initializer($this->_params); 1447 | if (count($this->_initCmds) > 0){ 1448 | $this->sendInitializationCommands(); 1449 | } 1450 | } 1451 | 1452 | private function tcpStreamInitializer(Predis_ConnectionParameters $parameters) { 1453 | return $this->redisStreamInitializer($parameters); 1454 | } 1455 | 1456 | private function redisStreamInitializer(Predis_ConnectionParameters $parameters) { 1457 | $uri = sprintf('tcp://%s:%d/', $parameters->host, $parameters->port); 1458 | $connectFlags = STREAM_CLIENT_CONNECT; 1459 | if ($parameters->connection_async) { 1460 | $connectFlags |= STREAM_CLIENT_ASYNC_CONNECT; 1461 | } 1462 | if ($parameters->connection_persistent) { 1463 | $connectFlags |= STREAM_CLIENT_PERSISTENT; 1464 | } 1465 | $socket = @stream_socket_client( 1466 | $uri, $errno, $errstr, $parameters->connection_timeout, $connectFlags 1467 | ); 1468 | 1469 | if (!$socket) { 1470 | $this->onCommunicationException(trim($errstr), $errno); 1471 | } 1472 | 1473 | if (isset($parameters->read_write_timeout)) { 1474 | $rwtimeout = $parameters->read_write_timeout; 1475 | $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; 1476 | $timeoutSeconds = floor($parameters->read_write_timeout); 1477 | $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000; 1478 | stream_set_timeout($socket, $rwtimeout, $timeoutUSeconds); 1479 | } 1480 | return $socket; 1481 | } 1482 | 1483 | private function unixStreamInitializer(Predis_ConnectionParameters $parameters) { 1484 | $uri = sprintf('unix:///%s', $parameters->path); 1485 | $connectFlags = STREAM_CLIENT_CONNECT; 1486 | if ($parameters->connection_persistent) { 1487 | $connectFlags |= STREAM_CLIENT_PERSISTENT; 1488 | } 1489 | $socket = @stream_socket_client( 1490 | $uri, $errno, $errstr, $parameters->connection_timeout, $connectFlags 1491 | ); 1492 | if (!$socket) { 1493 | $this->onCommunicationException(trim($errstr), $errno); 1494 | } 1495 | return $socket; 1496 | } 1497 | 1498 | public function disconnect() { 1499 | if ($this->isConnected()) { 1500 | fclose($this->_socket); 1501 | unset($this->_socket); 1502 | } 1503 | } 1504 | 1505 | public function pushInitCommand(Predis_Command $command){ 1506 | $this->_initCmds[] = $command; 1507 | } 1508 | 1509 | private function sendInitializationCommands() { 1510 | foreach ($this->_initCmds as $command) { 1511 | $this->writeCommand($command); 1512 | } 1513 | foreach ($this->_initCmds as $command) { 1514 | $this->readResponse($command); 1515 | } 1516 | } 1517 | 1518 | private function onCommunicationException($message, $code = null) { 1519 | Predis_Shared_Utils::onCommunicationException( 1520 | new Predis_CommunicationException($this, $message, $code) 1521 | ); 1522 | } 1523 | 1524 | public function writeCommand(Predis_Command $command) { 1525 | $this->writeBytes($command->invoke()); 1526 | } 1527 | 1528 | public function readResponse(Predis_Command $command) { 1529 | $response = $this->_reader->read($this); 1530 | return isset($response->skipParse) ? $response : $command->parseResponse($response); 1531 | } 1532 | 1533 | public function executeCommand(Predis_Command $command) { 1534 | $this->writeCommand($command); 1535 | if ($command->closesConnection()) { 1536 | return $this->disconnect(); 1537 | } 1538 | return $this->readResponse($command); 1539 | } 1540 | 1541 | public function rawCommand($rawCommandData, $closesConnection = false) { 1542 | $this->writeBytes($rawCommandData); 1543 | if ($closesConnection) { 1544 | $this->disconnect(); 1545 | return; 1546 | } 1547 | return $this->_reader->read($this); 1548 | } 1549 | 1550 | public function writeBytes($value) { 1551 | $socket = $this->getSocket(); 1552 | while (($length = strlen($value)) > 0) { 1553 | $written = fwrite($socket, $value); 1554 | if ($length === $written) { 1555 | return true; 1556 | } 1557 | if ($written === false || $written === 0) { 1558 | $this->onCommunicationException('Error while writing bytes to the server'); 1559 | } 1560 | $value = substr($value, $written); 1561 | } 1562 | return true; 1563 | } 1564 | 1565 | public function readBytes($length) { 1566 | if ($length <= 0) { 1567 | throw new InvalidArgumentException('Length parameter must be greater than 0'); 1568 | } 1569 | $socket = $this->getSocket(); 1570 | $value = ''; 1571 | do { 1572 | $chunk = fread($socket, $length); 1573 | if ($chunk === false || $chunk === '') { 1574 | $this->onCommunicationException('Error while reading bytes from the server'); 1575 | } 1576 | $value .= $chunk; 1577 | } 1578 | while (($length -= strlen($chunk)) > 0); 1579 | return $value; 1580 | } 1581 | 1582 | public function readLine() { 1583 | $socket = $this->getSocket(); 1584 | $value = ''; 1585 | do { 1586 | $chunk = fgets($socket); 1587 | if ($chunk === false || $chunk === '') { 1588 | $this->onCommunicationException('Error while reading line from the server'); 1589 | } 1590 | $value .= $chunk; 1591 | } 1592 | while (substr($value, -2) !== "\r\n"); 1593 | return substr($value, 0, -2); 1594 | } 1595 | 1596 | public function getSocket() { 1597 | if (isset($this->_socket)) { 1598 | return $this->_socket; 1599 | } 1600 | $this->connect(); 1601 | return $this->_socket; 1602 | } 1603 | 1604 | public function getResponseReader() { 1605 | return $this->_reader; 1606 | } 1607 | 1608 | public function getParameters() { 1609 | return $this->_params; 1610 | } 1611 | 1612 | public function __toString() { 1613 | return sprintf('%s:%d', $this->_params->host, $this->_params->port); 1614 | } 1615 | } 1616 | 1617 | class Predis_ConnectionCluster implements Predis_IConnection, IteratorAggregate { 1618 | private $_pool, $_distributor; 1619 | 1620 | public function __construct(Predis_Distribution_IDistributionStrategy $distributor = null) { 1621 | $this->_pool = array(); 1622 | $this->_distributor = $distributor !== null ? $distributor : new Predis_Distribution_HashRing(); 1623 | } 1624 | 1625 | public function isConnected() { 1626 | foreach ($this->_pool as $connection) { 1627 | if ($connection->isConnected()) { 1628 | return true; 1629 | } 1630 | } 1631 | return false; 1632 | } 1633 | 1634 | public function connect() { 1635 | foreach ($this->_pool as $connection) { 1636 | $connection->connect(); 1637 | } 1638 | } 1639 | 1640 | public function disconnect() { 1641 | foreach ($this->_pool as $connection) { 1642 | $connection->disconnect(); 1643 | } 1644 | } 1645 | 1646 | public function add(Predis_Connection $connection) { 1647 | $parameters = $connection->getParameters(); 1648 | if (isset($parameters->alias)) { 1649 | $this->_pool[$parameters->alias] = $connection; 1650 | } 1651 | else { 1652 | $this->_pool[] = $connection; 1653 | } 1654 | $this->_distributor->add($connection, $parameters->weight); 1655 | } 1656 | 1657 | public function getConnection(Predis_Command $command) { 1658 | if ($command->canBeHashed() === false) { 1659 | throw new Predis_ClientException( 1660 | sprintf("Cannot send '%s' commands to a cluster of connections", $command->getCommandId()) 1661 | ); 1662 | } 1663 | return $this->_distributor->get($command->getHash($this->_distributor)); 1664 | } 1665 | 1666 | public function getConnectionById($id = null) { 1667 | $alias = $id !== null ? $id : 0; 1668 | return isset($this->_pool[$alias]) ? $this->_pool[$alias] : null; 1669 | } 1670 | 1671 | public function getIterator() { 1672 | return new ArrayIterator($this->_pool); 1673 | } 1674 | 1675 | public function writeCommand(Predis_Command $command) { 1676 | $this->getConnection($command)->writeCommand($command); 1677 | } 1678 | 1679 | public function readResponse(Predis_Command $command) { 1680 | return $this->getConnection($command)->readResponse($command); 1681 | } 1682 | 1683 | public function executeCommand(Predis_Command $command) { 1684 | $connection = $this->getConnection($command); 1685 | $connection->writeCommand($command); 1686 | return $connection->readResponse($command); 1687 | } 1688 | } 1689 | 1690 | /* ------------------------------------------------------------------------- */ 1691 | 1692 | abstract class Predis_RedisServerProfile { 1693 | private static $_serverProfiles; 1694 | private $_registeredCommands; 1695 | 1696 | public function __construct() { 1697 | $this->_registeredCommands = $this->getSupportedCommands(); 1698 | } 1699 | 1700 | public abstract function getVersion(); 1701 | 1702 | protected abstract function getSupportedCommands(); 1703 | 1704 | public static function getDefault() { 1705 | return self::get('default'); 1706 | } 1707 | 1708 | public static function getDevelopment() { 1709 | return self::get('dev'); 1710 | } 1711 | 1712 | private static function predisServerProfiles() { 1713 | return array( 1714 | '1.2' => 'Predis_RedisServer_v1_2', 1715 | '2.0' => 'Predis_RedisServer_v2_0', 1716 | '2.2' => 'Predis_RedisServer_v2_2', 1717 | 'default' => 'Predis_RedisServer_v2_2', 1718 | 'dev' => 'Predis_RedisServer_vNext', 1719 | ); 1720 | } 1721 | 1722 | public static function registerProfile($profileClass, $aliases) { 1723 | if (!isset(self::$_serverProfiles)) { 1724 | self::$_serverProfiles = self::predisServerProfiles(); 1725 | } 1726 | 1727 | $profileReflection = new ReflectionClass($profileClass); 1728 | 1729 | if (!$profileReflection->isSubclassOf('Predis_RedisServerProfile')) { 1730 | throw new Predis_ClientException("Cannot register '$profileClass' as it is not a valid profile class"); 1731 | } 1732 | 1733 | if (is_array($aliases)) { 1734 | foreach ($aliases as $alias) { 1735 | self::$_serverProfiles[$alias] = $profileClass; 1736 | } 1737 | } 1738 | else { 1739 | self::$_serverProfiles[$aliases] = $profileClass; 1740 | } 1741 | } 1742 | 1743 | public static function get($version) { 1744 | if (!isset(self::$_serverProfiles)) { 1745 | self::$_serverProfiles = self::predisServerProfiles(); 1746 | } 1747 | if (!isset(self::$_serverProfiles[$version])) { 1748 | throw new Predis_ClientException("Unknown server profile: $version"); 1749 | } 1750 | $profile = self::$_serverProfiles[$version]; 1751 | return new $profile(); 1752 | } 1753 | 1754 | public function supportsCommands(Array $commands) { 1755 | foreach ($commands as $command) { 1756 | if ($this->supportsCommand($command) === false) { 1757 | return false; 1758 | } 1759 | } 1760 | return true; 1761 | } 1762 | 1763 | public function supportsCommand($command) { 1764 | return isset($this->_registeredCommands[$command]); 1765 | } 1766 | 1767 | public function createCommand($method, $arguments = array()) { 1768 | if (!isset($this->_registeredCommands[$method])) { 1769 | throw new Predis_ClientException("'$method' is not a registered Redis command"); 1770 | } 1771 | $commandClass = $this->_registeredCommands[$method]; 1772 | $command = new $commandClass(); 1773 | $command->setArgumentsArray($arguments); 1774 | return $command; 1775 | } 1776 | 1777 | public function registerCommands(Array $commands) { 1778 | foreach ($commands as $command => $aliases) { 1779 | $this->registerCommand($command, $aliases); 1780 | } 1781 | } 1782 | 1783 | public function registerCommand($command, $aliases) { 1784 | $commandReflection = new ReflectionClass($command); 1785 | 1786 | if (!$commandReflection->isSubclassOf('Predis_Command')) { 1787 | throw new ClientException("Cannot register '$command' as it is not a valid Redis command"); 1788 | } 1789 | 1790 | if (is_array($aliases)) { 1791 | foreach ($aliases as $alias) { 1792 | $this->_registeredCommands[$alias] = $command; 1793 | } 1794 | } 1795 | else { 1796 | $this->_registeredCommands[$aliases] = $command; 1797 | } 1798 | } 1799 | 1800 | public function __toString() { 1801 | return $this->getVersion(); 1802 | } 1803 | } 1804 | 1805 | class Predis_RedisServer_v1_2 extends Predis_RedisServerProfile { 1806 | public function getVersion() { return '1.2'; } 1807 | public function getSupportedCommands() { 1808 | return array( 1809 | /* ---------------- Redis 1.2 ---------------- */ 1810 | 1811 | /* miscellaneous commands */ 1812 | 'ping' => 'Predis_Commands_Ping', 1813 | 'echo' => 'Predis_Commands_DoEcho', 1814 | 'auth' => 'Predis_Commands_Auth', 1815 | 1816 | /* connection handling */ 1817 | 'quit' => 'Predis_Commands_Quit', 1818 | 1819 | /* commands operating on string values */ 1820 | 'set' => 'Predis_Commands_Set', 1821 | 'setnx' => 'Predis_Commands_SetPreserve', 1822 | 'mset' => 'Predis_Commands_SetMultiple', 1823 | 'msetnx' => 'Predis_Commands_SetMultiplePreserve', 1824 | 'get' => 'Predis_Commands_Get', 1825 | 'mget' => 'Predis_Commands_GetMultiple', 1826 | 'getset' => 'Predis_Commands_GetSet', 1827 | 'incr' => 'Predis_Commands_Increment', 1828 | 'incrby' => 'Predis_Commands_IncrementBy', 1829 | 'decr' => 'Predis_Commands_Decrement', 1830 | 'decrby' => 'Predis_Commands_DecrementBy', 1831 | 'exists' => 'Predis_Commands_Exists', 1832 | 'del' => 'Predis_Commands_Delete', 1833 | 'type' => 'Predis_Commands_Type', 1834 | 1835 | /* commands operating on the key space */ 1836 | 'keys' => 'Predis_Commands_Keys_v1_2', 1837 | 'randomkey' => 'Predis_Commands_RandomKey', 1838 | 'rename' => 'Predis_Commands_Rename', 1839 | 'renamenx' => 'Predis_Commands_RenamePreserve', 1840 | 'expire' => 'Predis_Commands_Expire', 1841 | 'expireat' => 'Predis_Commands_ExpireAt', 1842 | 'dbsize' => 'Predis_Commands_DatabaseSize', 1843 | 'ttl' => 'Predis_Commands_TimeToLive', 1844 | 1845 | /* commands operating on lists */ 1846 | 'rpush' => 'Predis_Commands_ListPushTail', 1847 | 'lpush' => 'Predis_Commands_ListPushHead', 1848 | 'llen' => 'Predis_Commands_ListLength', 1849 | 'lrange' => 'Predis_Commands_ListRange', 1850 | 'ltrim' => 'Predis_Commands_ListTrim', 1851 | 'lindex' => 'Predis_Commands_ListIndex', 1852 | 'lset' => 'Predis_Commands_ListSet', 1853 | 'lrem' => 'Predis_Commands_ListRemove', 1854 | 'lpop' => 'Predis_Commands_ListPopFirst', 1855 | 'rpop' => 'Predis_Commands_ListPopLast', 1856 | 'rpoplpush' => 'Predis_Commands_ListPopLastPushHead', 1857 | 1858 | /* commands operating on sets */ 1859 | 'sadd' => 'Predis_Commands_SetAdd', 1860 | 'srem' => 'Predis_Commands_SetRemove', 1861 | 'spop' => 'Predis_Commands_SetPop', 1862 | 'smove' => 'Predis_Commands_SetMove', 1863 | 'scard' => 'Predis_Commands_SetCardinality', 1864 | 'sismember' => 'Predis_Commands_SetIsMember', 1865 | 'sinter' => 'Predis_Commands_SetIntersection', 1866 | 'sinterstore' => 'Predis_Commands_SetIntersectionStore', 1867 | 'sunion' => 'Predis_Commands_SetUnion', 1868 | 'sunionstore' => 'Predis_Commands_SetUnionStore', 1869 | 'sdiff' => 'Predis_Commands_SetDifference', 1870 | 'sdiffstore' => 'Predis_Commands_SetDifferenceStore', 1871 | 'smembers' => 'Predis_Commands_SetMembers', 1872 | 'srandmember' => 'Predis_Commands_SetRandomMember', 1873 | 1874 | /* commands operating on sorted sets */ 1875 | 'zadd' => 'Predis_Commands_ZSetAdd', 1876 | 'zincrby' => 'Predis_Commands_ZSetIncrementBy', 1877 | 'zrem' => 'Predis_Commands_ZSetRemove', 1878 | 'zrange' => 'Predis_Commands_ZSetRange', 1879 | 'zrevrange' => 'Predis_Commands_ZSetReverseRange', 1880 | 'zrangebyscore' => 'Predis_Commands_ZSetRangeByScore', 1881 | 'zcard' => 'Predis_Commands_ZSetCardinality', 1882 | 'zscore' => 'Predis_Commands_ZSetScore', 1883 | 'zremrangebyscore' => 'Predis_Commands_ZSetRemoveRangeByScore', 1884 | 1885 | /* multiple databases handling commands */ 1886 | 'select' => 'Predis_Commands_SelectDatabase', 1887 | 'move' => 'Predis_Commands_MoveKey', 1888 | 'flushdb' => 'Predis_Commands_FlushDatabase', 1889 | 'flushall' => 'Predis_Commands_FlushAll', 1890 | 1891 | /* sorting */ 1892 | 'sort' => 'Predis_Commands_Sort', 1893 | 1894 | /* remote server control commands */ 1895 | 'info' => 'Predis_Commands_Info', 1896 | 'slaveof' => 'Predis_Commands_SlaveOf', 1897 | 1898 | /* persistence control commands */ 1899 | 'save' => 'Predis_Commands_Save', 1900 | 'bgsave' => 'Predis_Commands_BackgroundSave', 1901 | 'lastsave' => 'Predis_Commands_LastSave', 1902 | 'shutdown' => 'Predis_Commands_Shutdown', 1903 | 'bgrewriteaof' => 'Predis_Commands_BackgroundRewriteAppendOnlyFile', 1904 | ); 1905 | } 1906 | } 1907 | 1908 | class Predis_RedisServer_v2_0 extends Predis_RedisServerProfile { 1909 | public function getVersion() { return '2.0'; } 1910 | public function getSupportedCommands() { 1911 | return array( 1912 | /* ---------------- Redis 1.2 ---------------- */ 1913 | 1914 | /* miscellaneous commands */ 1915 | 'ping' => 'Predis_Commands_Ping', 1916 | 'echo' => 'Predis_Commands_DoEcho', 1917 | 'auth' => 'Predis_Commands_Auth', 1918 | 1919 | /* connection handling */ 1920 | 'quit' => 'Predis_Commands_Quit', 1921 | 1922 | /* commands operating on string values */ 1923 | 'set' => 'Predis_Commands_Set', 1924 | 'setnx' => 'Predis_Commands_SetPreserve', 1925 | 'mset' => 'Predis_Commands_SetMultiple', 1926 | 'msetnx' => 'Predis_Commands_SetMultiplePreserve', 1927 | 'get' => 'Predis_Commands_Get', 1928 | 'mget' => 'Predis_Commands_GetMultiple', 1929 | 'getset' => 'Predis_Commands_GetSet', 1930 | 'incr' => 'Predis_Commands_Increment', 1931 | 'incrby' => 'Predis_Commands_IncrementBy', 1932 | 'decr' => 'Predis_Commands_Decrement', 1933 | 'decrby' => 'Predis_Commands_DecrementBy', 1934 | 'exists' => 'Predis_Commands_Exists', 1935 | 'del' => 'Predis_Commands_Delete', 1936 | 'type' => 'Predis_Commands_Type', 1937 | 1938 | /* commands operating on the key space */ 1939 | 'keys' => 'Predis_Commands_Keys', 1940 | 'randomkey' => 'Predis_Commands_RandomKey', 1941 | 'rename' => 'Predis_Commands_Rename', 1942 | 'renamenx' => 'Predis_Commands_RenamePreserve', 1943 | 'expire' => 'Predis_Commands_Expire', 1944 | 'expireat' => 'Predis_Commands_ExpireAt', 1945 | 'dbsize' => 'Predis_Commands_DatabaseSize', 1946 | 'ttl' => 'Predis_Commands_TimeToLive', 1947 | 1948 | /* commands operating on lists */ 1949 | 'rpush' => 'Predis_Commands_ListPushTail', 1950 | 'lpush' => 'Predis_Commands_ListPushHead', 1951 | 'llen' => 'Predis_Commands_ListLength', 1952 | 'lrange' => 'Predis_Commands_ListRange', 1953 | 'ltrim' => 'Predis_Commands_ListTrim', 1954 | 'lindex' => 'Predis_Commands_ListIndex', 1955 | 'lset' => 'Predis_Commands_ListSet', 1956 | 'lrem' => 'Predis_Commands_ListRemove', 1957 | 'lpop' => 'Predis_Commands_ListPopFirst', 1958 | 'rpop' => 'Predis_Commands_ListPopLast', 1959 | 'rpoplpush' => 'Predis_Commands_ListPopLastPushHead', 1960 | 1961 | /* commands operating on sets */ 1962 | 'sadd' => 'Predis_Commands_SetAdd', 1963 | 'srem' => 'Predis_Commands_SetRemove', 1964 | 'spop' => 'Predis_Commands_SetPop', 1965 | 'smove' => 'Predis_Commands_SetMove', 1966 | 'scard' => 'Predis_Commands_SetCardinality', 1967 | 'sismember' => 'Predis_Commands_SetIsMember', 1968 | 'sinter' => 'Predis_Commands_SetIntersection', 1969 | 'sinterstore' => 'Predis_Commands_SetIntersectionStore', 1970 | 'sunion' => 'Predis_Commands_SetUnion', 1971 | 'sunionstore' => 'Predis_Commands_SetUnionStore', 1972 | 'sdiff' => 'Predis_Commands_SetDifference', 1973 | 'sdiffstore' => 'Predis_Commands_SetDifferenceStore', 1974 | 'smembers' => 'Predis_Commands_SetMembers', 1975 | 'srandmember' => 'Predis_Commands_SetRandomMember', 1976 | 1977 | /* commands operating on sorted sets */ 1978 | 'zadd' => 'Predis_Commands_ZSetAdd', 1979 | 'zincrby' => 'Predis_Commands_ZSetIncrementBy', 1980 | 'zrem' => 'Predis_Commands_ZSetRemove', 1981 | 'zrange' => 'Predis_Commands_ZSetRange', 1982 | 'zrevrange' => 'Predis_Commands_ZSetReverseRange', 1983 | 'zrangebyscore' => 'Predis_Commands_ZSetRangeByScore', 1984 | 'zcard' => 'Predis_Commands_ZSetCardinality', 1985 | 'zscore' => 'Predis_Commands_ZSetScore', 1986 | 'zremrangebyscore' => 'Predis_Commands_ZSetRemoveRangeByScore', 1987 | 1988 | /* multiple databases handling commands */ 1989 | 'select' => 'Predis_Commands_SelectDatabase', 1990 | 'move' => 'Predis_Commands_MoveKey', 1991 | 'flushdb' => 'Predis_Commands_FlushDatabase', 1992 | 'flushall' => 'Predis_Commands_FlushAll', 1993 | 1994 | /* sorting */ 1995 | 'sort' => 'Predis_Commands_Sort', 1996 | 1997 | /* remote server control commands */ 1998 | 'info' => 'Predis_Commands_Info', 1999 | 'slaveof' => 'Predis_Commands_SlaveOf', 2000 | 2001 | /* persistence control commands */ 2002 | 'save' => 'Predis_Commands_Save', 2003 | 'bgsave' => 'Predis_Commands_BackgroundSave', 2004 | 'lastsave' => 'Predis_Commands_LastSave', 2005 | 'shutdown' => 'Predis_Commands_Shutdown', 2006 | 'bgrewriteaof' => 'Predis_Commands_BackgroundRewriteAppendOnlyFile', 2007 | 2008 | 2009 | /* ---------------- Redis 2.0 ---------------- */ 2010 | 2011 | /* transactions */ 2012 | 'multi' => 'Predis_Commands_Multi', 2013 | 'exec' => 'Predis_Commands_Exec', 2014 | 'discard' => 'Predis_Commands_Discard', 2015 | 2016 | /* commands operating on string values */ 2017 | 'setex' => 'Predis_Commands_SetExpire', 2018 | 'append' => 'Predis_Commands_Append', 2019 | 'substr' => 'Predis_Commands_Substr', 2020 | 2021 | /* commands operating on lists */ 2022 | 'blpop' => 'Predis_Commands_ListPopFirstBlocking', 2023 | 'brpop' => 'Predis_Commands_ListPopLastBlocking', 2024 | 2025 | /* commands operating on sorted sets */ 2026 | 'zunionstore' => 'Predis_Commands_ZSetUnionStore', 2027 | 'zinterstore' => 'Predis_Commands_ZSetIntersectionStore', 2028 | 'zcount' => 'Predis_Commands_ZSetCount', 2029 | 'zrank' => 'Predis_Commands_ZSetRank', 2030 | 'zrevrank' => 'Predis_Commands_ZSetReverseRank', 2031 | 'zremrangebyrank' => 'Predis_Commands_ZSetRemoveRangeByRank', 2032 | 2033 | /* commands operating on hashes */ 2034 | 'hset' => 'Predis_Commands_HashSet', 2035 | 'hsetnx' => 'Predis_Commands_HashSetPreserve', 2036 | 'hmset' => 'Predis_Commands_HashSetMultiple', 2037 | 'hincrby' => 'Predis_Commands_HashIncrementBy', 2038 | 'hget' => 'Predis_Commands_HashGet', 2039 | 'hmget' => 'Predis_Commands_HashGetMultiple', 2040 | 'hdel' => 'Predis_Commands_HashDelete', 2041 | 'hexists' => 'Predis_Commands_HashExists', 2042 | 'hlen' => 'Predis_Commands_HashLength', 2043 | 'hkeys' => 'Predis_Commands_HashKeys', 2044 | 'hvals' => 'Predis_Commands_HashValues', 2045 | 'hgetall' => 'Predis_Commands_HashGetAll', 2046 | 2047 | /* publish - subscribe */ 2048 | 'subscribe' => 'Predis_Commands_Subscribe', 2049 | 'unsubscribe' => 'Predis_Commands_Unsubscribe', 2050 | 'psubscribe' => 'Predis_Commands_SubscribeByPattern', 2051 | 'punsubscribe' => 'Predis_Commands_UnsubscribeByPattern', 2052 | 'publish' => 'Predis_Commands_Publish', 2053 | 2054 | /* remote server control commands */ 2055 | 'config' => 'Predis_Commands_Config', 2056 | ); 2057 | } 2058 | } 2059 | 2060 | class Predis_RedisServer_v2_2 extends Predis_RedisServerProfile { 2061 | public function getVersion() { return '2.2'; } 2062 | public function getSupportedCommands() { 2063 | return array( 2064 | /* ---------------- Redis 1.2 ---------------- */ 2065 | 2066 | /* miscellaneous commands */ 2067 | 'ping' => 'Predis_Commands_Ping', 2068 | 'echo' => 'Predis_Commands_DoEcho', 2069 | 'auth' => 'Predis_Commands_Auth', 2070 | 2071 | /* connection handling */ 2072 | 'quit' => 'Predis_Commands_Quit', 2073 | 2074 | /* commands operating on string values */ 2075 | 'set' => 'Predis_Commands_Set', 2076 | 'setnx' => 'Predis_Commands_SetPreserve', 2077 | 'mset' => 'Predis_Commands_SetMultiple', 2078 | 'msetnx' => 'Predis_Commands_SetMultiplePreserve', 2079 | 'get' => 'Predis_Commands_Get', 2080 | 'mget' => 'Predis_Commands_GetMultiple', 2081 | 'getset' => 'Predis_Commands_GetSet', 2082 | 'incr' => 'Predis_Commands_Increment', 2083 | 'incrby' => 'Predis_Commands_IncrementBy', 2084 | 'decr' => 'Predis_Commands_Decrement', 2085 | 'decrby' => 'Predis_Commands_DecrementBy', 2086 | 'exists' => 'Predis_Commands_Exists', 2087 | 'del' => 'Predis_Commands_Delete', 2088 | 'type' => 'Predis_Commands_Type', 2089 | 2090 | /* commands operating on the key space */ 2091 | 'keys' => 'Predis_Commands_Keys', 2092 | 'randomkey' => 'Predis_Commands_RandomKey', 2093 | 'rename' => 'Predis_Commands_Rename', 2094 | 'renamenx' => 'Predis_Commands_RenamePreserve', 2095 | 'expire' => 'Predis_Commands_Expire', 2096 | 'expireat' => 'Predis_Commands_ExpireAt', 2097 | 'dbsize' => 'Predis_Commands_DatabaseSize', 2098 | 'ttl' => 'Predis_Commands_TimeToLive', 2099 | 2100 | /* commands operating on lists */ 2101 | 'rpush' => 'Predis_Commands_ListPushTail', 2102 | 'lpush' => 'Predis_Commands_ListPushHead', 2103 | 'llen' => 'Predis_Commands_ListLength', 2104 | 'lrange' => 'Predis_Commands_ListRange', 2105 | 'ltrim' => 'Predis_Commands_ListTrim', 2106 | 'lindex' => 'Predis_Commands_ListIndex', 2107 | 'lset' => 'Predis_Commands_ListSet', 2108 | 'lrem' => 'Predis_Commands_ListRemove', 2109 | 'lpop' => 'Predis_Commands_ListPopFirst', 2110 | 'rpop' => 'Predis_Commands_ListPopLast', 2111 | 'rpoplpush' => 'Predis_Commands_ListPopLastPushHead', 2112 | 2113 | /* commands operating on sets */ 2114 | 'sadd' => 'Predis_Commands_SetAdd', 2115 | 'srem' => 'Predis_Commands_SetRemove', 2116 | 'spop' => 'Predis_Commands_SetPop', 2117 | 'smove' => 'Predis_Commands_SetMove', 2118 | 'scard' => 'Predis_Commands_SetCardinality', 2119 | 'sismember' => 'Predis_Commands_SetIsMember', 2120 | 'sinter' => 'Predis_Commands_SetIntersection', 2121 | 'sinterstore' => 'Predis_Commands_SetIntersectionStore', 2122 | 'sunion' => 'Predis_Commands_SetUnion', 2123 | 'sunionstore' => 'Predis_Commands_SetUnionStore', 2124 | 'sdiff' => 'Predis_Commands_SetDifference', 2125 | 'sdiffstore' => 'Predis_Commands_SetDifferenceStore', 2126 | 'smembers' => 'Predis_Commands_SetMembers', 2127 | 'srandmember' => 'Predis_Commands_SetRandomMember', 2128 | 2129 | /* commands operating on sorted sets */ 2130 | 'zadd' => 'Predis_Commands_ZSetAdd', 2131 | 'zincrby' => 'Predis_Commands_ZSetIncrementBy', 2132 | 'zrem' => 'Predis_Commands_ZSetRemove', 2133 | 'zrange' => 'Predis_Commands_ZSetRange', 2134 | 'zrevrange' => 'Predis_Commands_ZSetReverseRange', 2135 | 'zrangebyscore' => 'Predis_Commands_ZSetRangeByScore', 2136 | 'zcard' => 'Predis_Commands_ZSetCardinality', 2137 | 'zscore' => 'Predis_Commands_ZSetScore', 2138 | 'zremrangebyscore' => 'Predis_Commands_ZSetRemoveRangeByScore', 2139 | 2140 | /* multiple databases handling commands */ 2141 | 'select' => 'Predis_Commands_SelectDatabase', 2142 | 'move' => 'Predis_Commands_MoveKey', 2143 | 'flushdb' => 'Predis_Commands_FlushDatabase', 2144 | 'flushall' => 'Predis_Commands_FlushAll', 2145 | 2146 | /* sorting */ 2147 | 'sort' => 'Predis_Commands_Sort', 2148 | 2149 | /* remote server control commands */ 2150 | 'info' => 'Predis_Commands_Info', 2151 | 'slaveof' => 'Predis_Commands_SlaveOf', 2152 | 2153 | /* persistence control commands */ 2154 | 'save' => 'Predis_Commands_Save', 2155 | 'bgsave' => 'Predis_Commands_BackgroundSave', 2156 | 'lastsave' => 'Predis_Commands_LastSave', 2157 | 'shutdown' => 'Predis_Commands_Shutdown', 2158 | 'bgrewriteaof' => 'Predis_Commands_BackgroundRewriteAppendOnlyFile', 2159 | 2160 | 2161 | /* ---------------- Redis 2.0 ---------------- */ 2162 | 2163 | /* transactions */ 2164 | 'multi' => 'Predis_Commands_Multi', 2165 | 'exec' => 'Predis_Commands_Exec', 2166 | 'discard' => 'Predis_Commands_Discard', 2167 | 2168 | /* commands operating on string values */ 2169 | 'setex' => 'Predis_Commands_SetExpire', 2170 | 'append' => 'Predis_Commands_Append', 2171 | 'substr' => 'Predis_Commands_Substr', 2172 | 2173 | /* commands operating on lists */ 2174 | 'blpop' => 'Predis_Commands_ListPopFirstBlocking', 2175 | 'brpop' => 'Predis_Commands_ListPopLastBlocking', 2176 | 2177 | /* commands operating on sorted sets */ 2178 | 'zunionstore' => 'Predis_Commands_ZSetUnionStore', 2179 | 'zinterstore' => 'Predis_Commands_ZSetIntersectionStore', 2180 | 'zcount' => 'Predis_Commands_ZSetCount', 2181 | 'zrank' => 'Predis_Commands_ZSetRank', 2182 | 'zrevrank' => 'Predis_Commands_ZSetReverseRank', 2183 | 'zremrangebyrank' => 'Predis_Commands_ZSetRemoveRangeByRank', 2184 | 2185 | /* commands operating on hashes */ 2186 | 'hset' => 'Predis_Commands_HashSet', 2187 | 'hsetnx' => 'Predis_Commands_HashSetPreserve', 2188 | 'hmset' => 'Predis_Commands_HashSetMultiple', 2189 | 'hincrby' => 'Predis_Commands_HashIncrementBy', 2190 | 'hget' => 'Predis_Commands_HashGet', 2191 | 'hmget' => 'Predis_Commands_HashGetMultiple', 2192 | 'hdel' => 'Predis_Commands_HashDelete', 2193 | 'hexists' => 'Predis_Commands_HashExists', 2194 | 'hlen' => 'Predis_Commands_HashLength', 2195 | 'hkeys' => 'Predis_Commands_HashKeys', 2196 | 'hvals' => 'Predis_Commands_HashValues', 2197 | 'hgetall' => 'Predis_Commands_HashGetAll', 2198 | 2199 | /* publish - subscribe */ 2200 | 'subscribe' => 'Predis_Commands_Subscribe', 2201 | 'unsubscribe' => 'Predis_Commands_Unsubscribe', 2202 | 'psubscribe' => 'Predis_Commands_SubscribeByPattern', 2203 | 'punsubscribe' => 'Predis_Commands_UnsubscribeByPattern', 2204 | 'publish' => 'Predis_Commands_Publish', 2205 | 2206 | /* remote server control commands */ 2207 | 'config' => 'Predis_Commands_Config', 2208 | 2209 | 2210 | /* ---------------- Redis 2.2 ---------------- */ 2211 | 2212 | /* transactions */ 2213 | 'watch' => 'Predis_Commands_Watch', 2214 | 'unwatch' => 'Predis_Commands_Unwatch', 2215 | 2216 | /* commands operating on string values */ 2217 | 'strlen' => 'Predis_Commands_Strlen', 2218 | 'setrange' => 'Predis_Commands_SetRange', 2219 | 'getrange' => 'Predis_Commands_Substr', 2220 | 'setbit' => 'Predis_Commands_SetBit', 2221 | 'getbit' => 'Predis_Commands_GetBit', 2222 | 2223 | /* commands operating on the key space */ 2224 | 'persist' => 'Predis_Commands_Persist', 2225 | 2226 | /* commands operating on lists */ 2227 | 'rpushx' => 'Predis_Commands_ListPushTailX', 2228 | 'lpushx' => 'Predis_Commands_ListPushHeadX', 2229 | 'linsert' => 'Predis_Commands_ListInsert', 2230 | 'brpoplpush' => 'Predis_Commands_ListPopLastPushHeadBlocking', 2231 | 2232 | /* commands operating on sorted sets */ 2233 | 'zrevrangebyscore' => 'Predis_Commands_ZSetReverseRangeByScore', 2234 | ); 2235 | } 2236 | } 2237 | 2238 | class Predis_RedisServer_vNext extends Predis_RedisServer_v2_2 { 2239 | public function getVersion() { return 'DEV'; } 2240 | public function getSupportedCommands() { 2241 | return array_merge(parent::getSupportedCommands(), array( 2242 | /* remote server control commands */ 2243 | 'info' => 'Predis_Commands_Info_v24', 2244 | )); 2245 | } 2246 | } 2247 | 2248 | /* ------------------------------------------------------------------------- */ 2249 | 2250 | interface Predis_Pipeline_IPipelineExecutor { 2251 | public function execute(Predis_IConnection $connection, &$commands); 2252 | } 2253 | 2254 | class Predis_Pipeline_StandardExecutor implements Predis_Pipeline_IPipelineExecutor { 2255 | public function execute(Predis_IConnection $connection, &$commands) { 2256 | $sizeofPipe = count($commands); 2257 | $values = array(); 2258 | 2259 | foreach ($commands as $command) { 2260 | $connection->writeCommand($command); 2261 | } 2262 | try { 2263 | for ($i = 0; $i < $sizeofPipe; $i++) { 2264 | $response = $connection->readResponse($commands[$i]); 2265 | $values[] = $response instanceof Iterator 2266 | ? iterator_to_array($response) 2267 | : $response; 2268 | unset($commands[$i]); 2269 | } 2270 | } 2271 | catch (Predis_ServerException $exception) { 2272 | // force disconnection to prevent protocol desynchronization 2273 | $connection->disconnect(); 2274 | throw $exception; 2275 | } 2276 | 2277 | return $values; 2278 | } 2279 | } 2280 | 2281 | class Predis_Pipeline_SafeExecutor implements Predis_Pipeline_IPipelineExecutor { 2282 | public function execute(Predis_IConnection $connection, &$commands) { 2283 | $sizeofPipe = count($commands); 2284 | $values = array(); 2285 | 2286 | foreach ($commands as $command) { 2287 | try { 2288 | $connection->writeCommand($command); 2289 | } 2290 | catch (Predis_CommunicationException $exception) { 2291 | return array_fill(0, $sizeofPipe, $exception); 2292 | } 2293 | } 2294 | 2295 | for ($i = 0; $i < $sizeofPipe; $i++) { 2296 | $command = $commands[$i]; 2297 | unset($commands[$i]); 2298 | try { 2299 | $response = $connection->readResponse($command); 2300 | $values[] = ($response instanceof Iterator 2301 | ? iterator_to_array($response) 2302 | : $response 2303 | ); 2304 | } 2305 | catch (Predis_ServerException $exception) { 2306 | $values[] = $exception->toResponseError(); 2307 | } 2308 | catch (Predis_CommunicationException $exception) { 2309 | $toAdd = count($commands) - count($values); 2310 | $values = array_merge($values, array_fill(0, $toAdd, $exception)); 2311 | break; 2312 | } 2313 | } 2314 | 2315 | return $values; 2316 | } 2317 | } 2318 | 2319 | class Predis_Pipeline_SafeClusterExecutor implements Predis_Pipeline_IPipelineExecutor { 2320 | public function execute(Predis_IConnection $connection, &$commands) { 2321 | $connectionExceptions = array(); 2322 | $sizeofPipe = count($commands); 2323 | $values = array(); 2324 | 2325 | foreach ($commands as $command) { 2326 | $cmdConnection = $connection->getConnection($command); 2327 | if (isset($connectionExceptions[spl_object_hash($cmdConnection)])) { 2328 | continue; 2329 | } 2330 | try { 2331 | $cmdConnection->writeCommand($command); 2332 | } 2333 | catch (Predis_CommunicationException $exception) { 2334 | $connectionExceptions[spl_object_hash($cmdConnection)] = $exception; 2335 | } 2336 | } 2337 | 2338 | for ($i = 0; $i < $sizeofPipe; $i++) { 2339 | $command = $commands[$i]; 2340 | unset($commands[$i]); 2341 | 2342 | $cmdConnection = $connection->getConnection($command); 2343 | $connectionObjectHash = spl_object_hash($cmdConnection); 2344 | 2345 | if (isset($connectionExceptions[$connectionObjectHash])) { 2346 | $values[] = $connectionExceptions[$connectionObjectHash]; 2347 | continue; 2348 | } 2349 | 2350 | try { 2351 | $response = $cmdConnection->readResponse($command); 2352 | $values[] = ($response instanceof Iterator 2353 | ? iterator_to_array($response) 2354 | : $response 2355 | ); 2356 | } 2357 | catch (Predis_ServerException $exception) { 2358 | $values[] = $exception->toResponseError(); 2359 | } 2360 | catch (Predis_CommunicationException $exception) { 2361 | $values[] = $exception; 2362 | $connectionExceptions[$connectionObjectHash] = $exception; 2363 | } 2364 | } 2365 | 2366 | return $values; 2367 | } 2368 | } 2369 | 2370 | /* ------------------------------------------------------------------------- */ 2371 | 2372 | interface Predis_Distribution_IDistributionStrategy { 2373 | public function add($node, $weight = null); 2374 | public function remove($node); 2375 | public function get($key); 2376 | public function generateKey($value); 2377 | } 2378 | 2379 | class Predis_Distribution_EmptyRingException extends Exception { } 2380 | 2381 | class Predis_Distribution_HashRing implements Predis_Distribution_IDistributionStrategy { 2382 | const DEFAULT_REPLICAS = 128; 2383 | const DEFAULT_WEIGHT = 100; 2384 | private $_nodes, $_ring, $_ringKeys, $_ringKeysCount, $_replicas; 2385 | 2386 | public function __construct($replicas = self::DEFAULT_REPLICAS) { 2387 | $this->_replicas = $replicas; 2388 | $this->_nodes = array(); 2389 | } 2390 | 2391 | public function add($node, $weight = null) { 2392 | // NOTE: in case of collisions in the hashes of the nodes, the node added 2393 | // last wins, thus the order in which nodes are added is significant. 2394 | // TODO: self::DEFAULT_WEIGHT does not work for inherited classes that 2395 | // override the DEFAULT_WEIGHT constant. 2396 | $this->_nodes[] = array( 2397 | 'object' => $node, 2398 | 'weight' => (int) ($weight !== null ? $weight : self::DEFAULT_WEIGHT), 2399 | ); 2400 | $this->reset(); 2401 | } 2402 | 2403 | public function remove($node) { 2404 | // NOTE: a node is removed by resetting the ring so that it's recreated from 2405 | // scratch, in order to reassign possible hashes with collisions to the 2406 | // right node according to the order in which they were added in the 2407 | // first place. 2408 | for ($i = 0; $i < count($this->_nodes); ++$i) { 2409 | if ($this->_nodes[$i]['object'] === $node) { 2410 | array_splice($this->_nodes, $i, 1); 2411 | $this->reset(); 2412 | break; 2413 | } 2414 | } 2415 | } 2416 | 2417 | private function reset() { 2418 | unset($this->_ring); 2419 | unset($this->_ringKeys); 2420 | unset($this->_ringKeysCount); 2421 | } 2422 | 2423 | private function isInitialized() { 2424 | return isset($this->_ringKeys); 2425 | } 2426 | 2427 | private function computeTotalWeight() { 2428 | $totalWeight = 0; 2429 | foreach ($this->_nodes as $node) { 2430 | $totalWeight += $node['weight']; 2431 | } 2432 | return $totalWeight; 2433 | } 2434 | 2435 | private function initialize() { 2436 | if ($this->isInitialized()) { 2437 | return; 2438 | } 2439 | if (count($this->_nodes) === 0) { 2440 | throw new Predis_Distribution_EmptyRingException('Cannot initialize empty hashring'); 2441 | } 2442 | 2443 | $this->_ring = array(); 2444 | $totalWeight = $this->computeTotalWeight(); 2445 | $nodesCount = count($this->_nodes); 2446 | foreach ($this->_nodes as $node) { 2447 | $weightRatio = $node['weight'] / $totalWeight; 2448 | $this->addNodeToRing($this->_ring, $node, $nodesCount, $this->_replicas, $weightRatio); 2449 | } 2450 | ksort($this->_ring, SORT_NUMERIC); 2451 | $this->_ringKeys = array_keys($this->_ring); 2452 | $this->_ringKeysCount = count($this->_ringKeys); 2453 | } 2454 | 2455 | protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio) { 2456 | $nodeObject = $node['object']; 2457 | $nodeHash = (string) $nodeObject; 2458 | $replicas = (int) round($weightRatio * $totalNodes * $replicas); 2459 | for ($i = 0; $i < $replicas; $i++) { 2460 | $key = crc32("$nodeHash:$i"); 2461 | $ring[$key] = $nodeObject; 2462 | } 2463 | } 2464 | 2465 | public function generateKey($value) { 2466 | return crc32($value); 2467 | } 2468 | 2469 | public function get($key) { 2470 | return $this->_ring[$this->getNodeKey($key)]; 2471 | } 2472 | 2473 | private function getNodeKey($key) { 2474 | $this->initialize(); 2475 | $ringKeys = $this->_ringKeys; 2476 | $upper = $this->_ringKeysCount - 1; 2477 | $lower = 0; 2478 | 2479 | while ($lower <= $upper) { 2480 | $index = ($lower + $upper) >> 1; 2481 | $item = $ringKeys[$index]; 2482 | if ($item > $key) { 2483 | $upper = $index - 1; 2484 | } 2485 | else if ($item < $key) { 2486 | $lower = $index + 1; 2487 | } 2488 | else { 2489 | return $item; 2490 | } 2491 | } 2492 | return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->_ringKeysCount)]; 2493 | } 2494 | 2495 | protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) { 2496 | // NOTE: binary search for the last item in _ringkeys with a value 2497 | // less or equal to the key. If no such item exists, return the 2498 | // last item. 2499 | return $upper >= 0 ? $upper : $ringKeysCount - 1; 2500 | } 2501 | } 2502 | 2503 | class Predis_Distribution_KetamaPureRing extends Predis_Distribution_HashRing { 2504 | const DEFAULT_REPLICAS = 160; 2505 | 2506 | public function __construct() { 2507 | parent::__construct(self::DEFAULT_REPLICAS); 2508 | } 2509 | 2510 | protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio) { 2511 | $nodeObject = $node['object']; 2512 | $nodeHash = (string) $nodeObject; 2513 | $replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4)); 2514 | for ($i = 0; $i < $replicas; $i++) { 2515 | $unpackedDigest = unpack('V4', md5("$nodeHash-$i", true)); 2516 | foreach ($unpackedDigest as $key) { 2517 | $ring[$key] = $nodeObject; 2518 | } 2519 | } 2520 | } 2521 | 2522 | public function generateKey($value) { 2523 | $hash = unpack('V', md5($value, true)); 2524 | return $hash[1]; 2525 | } 2526 | 2527 | protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) { 2528 | // NOTE: binary search for the first item in _ringkeys with a value 2529 | // greater or equal to the key. If no such item exists, return the 2530 | // first item. 2531 | return $lower < $ringKeysCount ? $lower : 0; 2532 | } 2533 | } 2534 | 2535 | /* ------------------------------------------------------------------------- */ 2536 | 2537 | class Predis_Shared_Utils { 2538 | public static function isCluster(Predis_IConnection $connection) { 2539 | return $connection instanceof Predis_ConnectionCluster; 2540 | } 2541 | 2542 | public static function onCommunicationException(Predis_CommunicationException $exception) { 2543 | if ($exception->shouldResetConnection()) { 2544 | $connection = $exception->getConnection(); 2545 | if ($connection->isConnected()) { 2546 | $connection->disconnect(); 2547 | } 2548 | } 2549 | throw $exception; 2550 | } 2551 | 2552 | public static function filterArrayArguments(Array $arguments) { 2553 | if (count($arguments) === 1 && is_array($arguments[0])) { 2554 | return $arguments[0]; 2555 | } 2556 | return $arguments; 2557 | } 2558 | } 2559 | 2560 | abstract class Predis_Shared_MultiBulkResponseIteratorBase implements Iterator, Countable { 2561 | protected $_position, $_current, $_replySize; 2562 | 2563 | public function rewind() { 2564 | // NOOP 2565 | } 2566 | 2567 | public function current() { 2568 | return $this->_current; 2569 | } 2570 | 2571 | public function key() { 2572 | return $this->_position; 2573 | } 2574 | 2575 | public function next() { 2576 | if (++$this->_position < $this->_replySize) { 2577 | $this->_current = $this->getValue(); 2578 | } 2579 | return $this->_position; 2580 | } 2581 | 2582 | public function valid() { 2583 | return $this->_position < $this->_replySize; 2584 | } 2585 | 2586 | public function count() { 2587 | // NOTE: use count if you want to get the size of the current multi-bulk 2588 | // response without using iterator_count (which actually consumes 2589 | // our iterator to calculate the size, and we cannot perform a rewind) 2590 | return $this->_replySize; 2591 | } 2592 | 2593 | protected abstract function getValue(); 2594 | } 2595 | 2596 | class Predis_Shared_MultiBulkResponseIterator extends Predis_Shared_MultiBulkResponseIteratorBase { 2597 | private $_connection; 2598 | 2599 | public function __construct(Predis_Connection $connection, $size) { 2600 | $this->_connection = $connection; 2601 | $this->_reader = $connection->getResponseReader(); 2602 | $this->_position = 0; 2603 | $this->_current = $size > 0 ? $this->getValue() : null; 2604 | $this->_replySize = $size; 2605 | } 2606 | 2607 | public function __destruct() { 2608 | // when the iterator is garbage-collected (e.g. it goes out of the 2609 | // scope of a foreach) but it has not reached its end, we must sync 2610 | // the client with the queued elements that have not been read from 2611 | // the connection with the server. 2612 | $this->sync(); 2613 | } 2614 | 2615 | public function sync($drop = false) { 2616 | if ($drop == true) { 2617 | if ($this->valid()) { 2618 | $this->_position = $this->_replySize; 2619 | $this->_connection->disconnect(); 2620 | } 2621 | } 2622 | else { 2623 | while ($this->valid()) { 2624 | $this->next(); 2625 | } 2626 | } 2627 | } 2628 | 2629 | protected function getValue() { 2630 | return $this->_reader->read($this->_connection); 2631 | } 2632 | } 2633 | 2634 | class Predis_Shared_MultiBulkResponseKVIterator extends Predis_Shared_MultiBulkResponseIteratorBase { 2635 | private $_iterator; 2636 | 2637 | public function __construct(Predis_Shared_MultiBulkResponseIterator $iterator) { 2638 | $virtualSize = count($iterator) / 2; 2639 | 2640 | $this->_iterator = $iterator; 2641 | $this->_position = 0; 2642 | $this->_current = $virtualSize > 0 ? $this->getValue() : null; 2643 | $this->_replySize = $virtualSize; 2644 | } 2645 | 2646 | public function __destruct() { 2647 | $this->_iterator->sync(); 2648 | } 2649 | 2650 | protected function getValue() { 2651 | $k = $this->_iterator->current(); 2652 | $this->_iterator->next(); 2653 | $v = $this->_iterator->current(); 2654 | $this->_iterator->next(); 2655 | return array($k, $v); 2656 | } 2657 | } 2658 | 2659 | /* ------------------------------------------------------------------------- */ 2660 | 2661 | /* miscellaneous commands */ 2662 | class Predis_Commands_Ping extends Predis_MultiBulkCommand { 2663 | public function canBeHashed() { return false; } 2664 | public function getCommandId() { return 'PING'; } 2665 | public function parseResponse($data) { 2666 | return $data === 'PONG'; 2667 | } 2668 | } 2669 | 2670 | class Predis_Commands_DoEcho extends Predis_MultiBulkCommand { 2671 | public function canBeHashed() { return false; } 2672 | public function getCommandId() { return 'ECHO'; } 2673 | } 2674 | 2675 | class Predis_Commands_Auth extends Predis_MultiBulkCommand { 2676 | public function canBeHashed() { return false; } 2677 | public function getCommandId() { return 'AUTH'; } 2678 | } 2679 | 2680 | /* connection handling */ 2681 | class Predis_Commands_Quit extends Predis_MultiBulkCommand { 2682 | public function canBeHashed() { return false; } 2683 | public function getCommandId() { return 'QUIT'; } 2684 | public function closesConnection() { return true; } 2685 | } 2686 | 2687 | /* commands operating on string values */ 2688 | class Predis_Commands_Set extends Predis_MultiBulkCommand { 2689 | public function getCommandId() { return 'SET'; } 2690 | } 2691 | 2692 | class Predis_Commands_SetExpire extends Predis_MultiBulkCommand { 2693 | public function getCommandId() { return 'SETEX'; } 2694 | } 2695 | 2696 | class Predis_Commands_SetPreserve extends Predis_MultiBulkCommand { 2697 | public function getCommandId() { return 'SETNX'; } 2698 | public function parseResponse($data) { return (bool) $data; } 2699 | } 2700 | 2701 | class Predis_Commands_SetMultiple extends Predis_MultiBulkCommand { 2702 | public function canBeHashed() { return false; } 2703 | public function getCommandId() { return 'MSET'; } 2704 | public function filterArguments(Array $arguments) { 2705 | if (count($arguments) === 1 && is_array($arguments[0])) { 2706 | $flattenedKVs = array(); 2707 | $args = &$arguments[0]; 2708 | foreach ($args as $k => $v) { 2709 | $flattenedKVs[] = $k; 2710 | $flattenedKVs[] = $v; 2711 | } 2712 | return $flattenedKVs; 2713 | } 2714 | return $arguments; 2715 | } 2716 | } 2717 | 2718 | class Predis_Commands_SetMultiplePreserve extends Predis_Commands_SetMultiple { 2719 | public function canBeHashed() { return false; } 2720 | public function getCommandId() { return 'MSETNX'; } 2721 | public function parseResponse($data) { return (bool) $data; } 2722 | } 2723 | 2724 | class Predis_Commands_Get extends Predis_MultiBulkCommand { 2725 | public function getCommandId() { return 'GET'; } 2726 | } 2727 | 2728 | class Predis_Commands_GetMultiple extends Predis_MultiBulkCommand { 2729 | public function canBeHashed() { return false; } 2730 | public function getCommandId() { return 'MGET'; } 2731 | public function filterArguments(Array $arguments) { 2732 | return Predis_Shared_Utils::filterArrayArguments($arguments); 2733 | } 2734 | } 2735 | 2736 | class Predis_Commands_GetSet extends Predis_MultiBulkCommand { 2737 | public function getCommandId() { return 'GETSET'; } 2738 | } 2739 | 2740 | class Predis_Commands_Increment extends Predis_MultiBulkCommand { 2741 | public function getCommandId() { return 'INCR'; } 2742 | } 2743 | 2744 | class Predis_Commands_IncrementBy extends Predis_MultiBulkCommand { 2745 | public function getCommandId() { return 'INCRBY'; } 2746 | } 2747 | 2748 | class Predis_Commands_Decrement extends Predis_MultiBulkCommand { 2749 | public function getCommandId() { return 'DECR'; } 2750 | } 2751 | 2752 | class Predis_Commands_DecrementBy extends Predis_MultiBulkCommand { 2753 | public function getCommandId() { return 'DECRBY'; } 2754 | } 2755 | 2756 | class Predis_Commands_Exists extends Predis_MultiBulkCommand { 2757 | public function getCommandId() { return 'EXISTS'; } 2758 | public function parseResponse($data) { return (bool) $data; } 2759 | } 2760 | 2761 | class Predis_Commands_Delete extends Predis_MultiBulkCommand { 2762 | public function getCommandId() { return 'DEL'; } 2763 | } 2764 | 2765 | class Predis_Commands_Type extends Predis_MultiBulkCommand { 2766 | public function getCommandId() { return 'TYPE'; } 2767 | } 2768 | 2769 | class Predis_Commands_Append extends Predis_MultiBulkCommand { 2770 | public function getCommandId() { return 'APPEND'; } 2771 | } 2772 | 2773 | class Predis_Commands_SetRange extends Predis_MultiBulkCommand { 2774 | public function getCommandId() { return 'SETRANGE'; } 2775 | } 2776 | 2777 | class Predis_Commands_Substr extends Predis_MultiBulkCommand { 2778 | public function getCommandId() { return 'SUBSTR'; } 2779 | } 2780 | 2781 | class Predis_Commands_SetBit extends Predis_MultiBulkCommand { 2782 | public function getCommandId() { return 'SETBIT'; } 2783 | } 2784 | 2785 | class Predis_Commands_GetBit extends Predis_MultiBulkCommand { 2786 | public function getCommandId() { return 'GETBIT'; } 2787 | } 2788 | 2789 | class Predis_Commands_Strlen extends Predis_MultiBulkCommand { 2790 | public function getCommandId() { return 'STRLEN'; } 2791 | } 2792 | 2793 | /* commands operating on the key space */ 2794 | class Predis_Commands_Keys extends Predis_MultiBulkCommand { 2795 | public function canBeHashed() { return false; } 2796 | public function getCommandId() { return 'KEYS'; } 2797 | } 2798 | 2799 | class Predis_Commands_Keys_v1_2 extends Predis_Commands_Keys { 2800 | public function parseResponse($data) { 2801 | return explode(' ', $data); 2802 | } 2803 | } 2804 | 2805 | class Predis_Commands_RandomKey extends Predis_MultiBulkCommand { 2806 | public function canBeHashed() { return false; } 2807 | public function getCommandId() { return 'RANDOMKEY'; } 2808 | public function parseResponse($data) { return $data !== '' ? $data : null; } 2809 | } 2810 | 2811 | class Predis_Commands_Rename extends Predis_MultiBulkCommand { 2812 | public function canBeHashed() { return false; } 2813 | public function getCommandId() { return 'RENAME'; } 2814 | } 2815 | 2816 | class Predis_Commands_RenamePreserve extends Predis_MultiBulkCommand { 2817 | public function canBeHashed() { return false; } 2818 | public function getCommandId() { return 'RENAMENX'; } 2819 | public function parseResponse($data) { return (bool) $data; } 2820 | } 2821 | 2822 | class Predis_Commands_Expire extends Predis_MultiBulkCommand { 2823 | public function getCommandId() { return 'EXPIRE'; } 2824 | public function parseResponse($data) { return (bool) $data; } 2825 | } 2826 | 2827 | class Predis_Commands_ExpireAt extends Predis_MultiBulkCommand { 2828 | public function getCommandId() { return 'EXPIREAT'; } 2829 | public function parseResponse($data) { return (bool) $data; } 2830 | } 2831 | 2832 | class Predis_Commands_Persist extends Predis_MultiBulkCommand { 2833 | public function getCommandId() { return 'PERSIST'; } 2834 | public function parseResponse($data) { return (bool) $data; } 2835 | } 2836 | 2837 | class Predis_Commands_DatabaseSize extends Predis_MultiBulkCommand { 2838 | public function canBeHashed() { return false; } 2839 | public function getCommandId() { return 'DBSIZE'; } 2840 | } 2841 | 2842 | class Predis_Commands_TimeToLive extends Predis_MultiBulkCommand { 2843 | public function getCommandId() { return 'TTL'; } 2844 | } 2845 | 2846 | /* commands operating on lists */ 2847 | class Predis_Commands_ListPushTail extends Predis_MultiBulkCommand { 2848 | public function getCommandId() { return 'RPUSH'; } 2849 | } 2850 | 2851 | class Predis_Commands_ListPushTailX extends Predis_MultiBulkCommand { 2852 | public function getCommandId() { return 'RPUSHX'; } 2853 | } 2854 | 2855 | class Predis_Commands_ListPushHead extends Predis_MultiBulkCommand { 2856 | public function getCommandId() { return 'LPUSH'; } 2857 | } 2858 | 2859 | class Predis_Commands_ListPushHeadX extends Predis_MultiBulkCommand { 2860 | public function getCommandId() { return 'LPUSHX'; } 2861 | } 2862 | 2863 | class Predis_Commands_ListLength extends Predis_MultiBulkCommand { 2864 | public function getCommandId() { return 'LLEN'; } 2865 | } 2866 | 2867 | class Predis_Commands_ListRange extends Predis_MultiBulkCommand { 2868 | public function getCommandId() { return 'LRANGE'; } 2869 | } 2870 | 2871 | class Predis_Commands_ListTrim extends Predis_MultiBulkCommand { 2872 | public function getCommandId() { return 'LTRIM'; } 2873 | } 2874 | 2875 | class Predis_Commands_ListIndex extends Predis_MultiBulkCommand { 2876 | public function getCommandId() { return 'LINDEX'; } 2877 | } 2878 | 2879 | class Predis_Commands_ListSet extends Predis_MultiBulkCommand { 2880 | public function getCommandId() { return 'LSET'; } 2881 | } 2882 | 2883 | class Predis_Commands_ListRemove extends Predis_MultiBulkCommand { 2884 | public function getCommandId() { return 'LREM'; } 2885 | } 2886 | 2887 | class Predis_Commands_ListPopLastPushHead extends Predis_MultiBulkCommand { 2888 | public function getCommandId() { return 'RPOPLPUSH'; } 2889 | } 2890 | 2891 | class Predis_Commands_ListPopLastPushHeadBlocking extends Predis_MultiBulkCommand { 2892 | public function getCommandId() { return 'BRPOPLPUSH'; } 2893 | } 2894 | 2895 | class Predis_Commands_ListPopFirst extends Predis_MultiBulkCommand { 2896 | public function getCommandId() { return 'LPOP'; } 2897 | } 2898 | 2899 | class Predis_Commands_ListPopLast extends Predis_MultiBulkCommand { 2900 | public function getCommandId() { return 'RPOP'; } 2901 | } 2902 | 2903 | class Predis_Commands_ListPopFirstBlocking extends Predis_MultiBulkCommand { 2904 | public function getCommandId() { return 'BLPOP'; } 2905 | } 2906 | 2907 | class Predis_Commands_ListPopLastBlocking extends Predis_MultiBulkCommand { 2908 | public function getCommandId() { return 'BRPOP'; } 2909 | } 2910 | 2911 | class Predis_Commands_ListInsert extends Predis_MultiBulkCommand { 2912 | public function getCommandId() { return 'LINSERT'; } 2913 | } 2914 | 2915 | /* commands operating on sets */ 2916 | class Predis_Commands_SetAdd extends Predis_MultiBulkCommand { 2917 | public function getCommandId() { return 'SADD'; } 2918 | public function parseResponse($data) { return (bool) $data; } 2919 | } 2920 | 2921 | class Predis_Commands_SetRemove extends Predis_MultiBulkCommand { 2922 | public function getCommandId() { return 'SREM'; } 2923 | public function parseResponse($data) { return (bool) $data; } 2924 | } 2925 | 2926 | class Predis_Commands_SetPop extends Predis_MultiBulkCommand { 2927 | public function getCommandId() { return 'SPOP'; } 2928 | } 2929 | 2930 | class Predis_Commands_SetMove extends Predis_MultiBulkCommand { 2931 | public function canBeHashed() { return false; } 2932 | public function getCommandId() { return 'SMOVE'; } 2933 | public function parseResponse($data) { return (bool) $data; } 2934 | } 2935 | 2936 | class Predis_Commands_SetCardinality extends Predis_MultiBulkCommand { 2937 | public function getCommandId() { return 'SCARD'; } 2938 | } 2939 | 2940 | class Predis_Commands_SetIsMember extends Predis_MultiBulkCommand { 2941 | public function getCommandId() { return 'SISMEMBER'; } 2942 | public function parseResponse($data) { return (bool) $data; } 2943 | } 2944 | 2945 | class Predis_Commands_SetIntersection extends Predis_MultiBulkCommand { 2946 | public function getCommandId() { return 'SINTER'; } 2947 | public function filterArguments(Array $arguments) { 2948 | return Predis_Shared_Utils::filterArrayArguments($arguments); 2949 | } 2950 | } 2951 | 2952 | class Predis_Commands_SetIntersectionStore extends Predis_MultiBulkCommand { 2953 | public function getCommandId() { return 'SINTERSTORE'; } 2954 | public function filterArguments(Array $arguments) { 2955 | if (count($arguments) === 2 && is_array($arguments[1])) { 2956 | return array_merge(array($arguments[0]), $arguments[1]); 2957 | } 2958 | return $arguments; 2959 | } 2960 | } 2961 | 2962 | class Predis_Commands_SetUnion extends Predis_Commands_SetIntersection { 2963 | public function getCommandId() { return 'SUNION'; } 2964 | } 2965 | 2966 | class Predis_Commands_SetUnionStore extends Predis_Commands_SetIntersectionStore { 2967 | public function getCommandId() { return 'SUNIONSTORE'; } 2968 | } 2969 | 2970 | class Predis_Commands_SetDifference extends Predis_MultiBulkCommand { 2971 | public function getCommandId() { return 'SDIFF'; } 2972 | } 2973 | 2974 | class Predis_Commands_SetDifferenceStore extends Predis_MultiBulkCommand { 2975 | public function getCommandId() { return 'SDIFFSTORE'; } 2976 | } 2977 | 2978 | class Predis_Commands_SetMembers extends Predis_MultiBulkCommand { 2979 | public function getCommandId() { return 'SMEMBERS'; } 2980 | } 2981 | 2982 | class Predis_Commands_SetRandomMember extends Predis_MultiBulkCommand { 2983 | public function getCommandId() { return 'SRANDMEMBER'; } 2984 | } 2985 | 2986 | /* commands operating on sorted sets */ 2987 | class Predis_Commands_ZSetAdd extends Predis_MultiBulkCommand { 2988 | public function getCommandId() { return 'ZADD'; } 2989 | public function parseResponse($data) { return (bool) $data; } 2990 | } 2991 | 2992 | class Predis_Commands_ZSetIncrementBy extends Predis_MultiBulkCommand { 2993 | public function getCommandId() { return 'ZINCRBY'; } 2994 | } 2995 | 2996 | class Predis_Commands_ZSetRemove extends Predis_MultiBulkCommand { 2997 | public function getCommandId() { return 'ZREM'; } 2998 | public function parseResponse($data) { return (bool) $data; } 2999 | } 3000 | 3001 | class Predis_Commands_ZSetUnionStore extends Predis_MultiBulkCommand { 3002 | public function getCommandId() { return 'ZUNIONSTORE'; } 3003 | public function filterArguments(Array $arguments) { 3004 | $options = array(); 3005 | $argc = count($arguments); 3006 | if ($argc > 2 && is_array($arguments[$argc - 1])) { 3007 | $options = $this->prepareOptions(array_pop($arguments)); 3008 | } 3009 | if (is_array($arguments[1])) { 3010 | $arguments = array_merge( 3011 | array($arguments[0], count($arguments[1])), 3012 | $arguments[1] 3013 | ); 3014 | } 3015 | return array_merge($arguments, $options); 3016 | } 3017 | private function prepareOptions($options) { 3018 | $opts = array_change_key_case($options, CASE_UPPER); 3019 | $finalizedOpts = array(); 3020 | if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) { 3021 | $finalizedOpts[] = 'WEIGHTS'; 3022 | foreach ($opts['WEIGHTS'] as $weight) { 3023 | $finalizedOpts[] = $weight; 3024 | } 3025 | } 3026 | if (isset($opts['AGGREGATE'])) { 3027 | $finalizedOpts[] = 'AGGREGATE'; 3028 | $finalizedOpts[] = $opts['AGGREGATE']; 3029 | } 3030 | return $finalizedOpts; 3031 | } 3032 | } 3033 | 3034 | class Predis_Commands_ZSetIntersectionStore extends Predis_Commands_ZSetUnionStore { 3035 | public function getCommandId() { return 'ZINTERSTORE'; } 3036 | } 3037 | 3038 | class Predis_Commands_ZSetRange extends Predis_MultiBulkCommand { 3039 | private $_withScores = false; 3040 | public function getCommandId() { return 'ZRANGE'; } 3041 | public function filterArguments(Array $arguments) { 3042 | if (count($arguments) === 4) { 3043 | $lastType = gettype($arguments[3]); 3044 | if ($lastType === 'string' && strtolower($arguments[3]) === 'withscores') { 3045 | // used for compatibility with older versions 3046 | $arguments[3] = array('WITHSCORES' => true); 3047 | $lastType = 'array'; 3048 | } 3049 | if ($lastType === 'array') { 3050 | $options = $this->prepareOptions(array_pop($arguments)); 3051 | return array_merge($arguments, $options); 3052 | } 3053 | } 3054 | return $arguments; 3055 | } 3056 | protected function prepareOptions($options) { 3057 | $opts = array_change_key_case($options, CASE_UPPER); 3058 | $finalizedOpts = array(); 3059 | if (isset($opts['WITHSCORES'])) { 3060 | $finalizedOpts[] = 'WITHSCORES'; 3061 | $this->_withScores = true; 3062 | } 3063 | return $finalizedOpts; 3064 | } 3065 | public function parseResponse($data) { 3066 | if ($this->_withScores) { 3067 | if ($data instanceof Iterator) { 3068 | return new Predis_Shared_MultiBulkResponseKVIterator($data); 3069 | } 3070 | $result = array(); 3071 | for ($i = 0; $i < count($data); $i++) { 3072 | $result[] = array($data[$i], $data[++$i]); 3073 | } 3074 | return $result; 3075 | } 3076 | return $data; 3077 | } 3078 | } 3079 | 3080 | class Predis_Commands_ZSetReverseRange extends Predis_Commands_ZSetRange { 3081 | public function getCommandId() { return 'ZREVRANGE'; } 3082 | } 3083 | 3084 | class Predis_Commands_ZSetRangeByScore extends Predis_Commands_ZSetRange { 3085 | public function getCommandId() { return 'ZRANGEBYSCORE'; } 3086 | protected function prepareOptions($options) { 3087 | $opts = array_change_key_case($options, CASE_UPPER); 3088 | $finalizedOpts = array(); 3089 | if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) { 3090 | $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER); 3091 | $finalizedOpts[] = 'LIMIT'; 3092 | $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0]; 3093 | $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1]; 3094 | } 3095 | return array_merge($finalizedOpts, parent::prepareOptions($options)); 3096 | } 3097 | } 3098 | 3099 | class Predis_Commands_ZSetReverseRangeByScore extends Predis_Commands_ZSetRangeByScore { 3100 | public function getCommandId() { return 'ZREVRANGEBYSCORE'; } 3101 | } 3102 | 3103 | class Predis_Commands_ZSetCount extends Predis_MultiBulkCommand { 3104 | public function getCommandId() { return 'ZCOUNT'; } 3105 | } 3106 | 3107 | class Predis_Commands_ZSetCardinality extends Predis_MultiBulkCommand { 3108 | public function getCommandId() { return 'ZCARD'; } 3109 | } 3110 | 3111 | class Predis_Commands_ZSetScore extends Predis_MultiBulkCommand { 3112 | public function getCommandId() { return 'ZSCORE'; } 3113 | } 3114 | 3115 | class Predis_Commands_ZSetRemoveRangeByScore extends Predis_MultiBulkCommand { 3116 | public function getCommandId() { return 'ZREMRANGEBYSCORE'; } 3117 | } 3118 | 3119 | class Predis_Commands_ZSetRank extends Predis_MultiBulkCommand { 3120 | public function getCommandId() { return 'ZRANK'; } 3121 | } 3122 | 3123 | class Predis_Commands_ZSetReverseRank extends Predis_MultiBulkCommand { 3124 | public function getCommandId() { return 'ZREVRANK'; } 3125 | } 3126 | 3127 | class Predis_Commands_ZSetRemoveRangeByRank extends Predis_MultiBulkCommand { 3128 | public function getCommandId() { return 'ZREMRANGEBYRANK'; } 3129 | } 3130 | 3131 | /* commands operating on hashes */ 3132 | class Predis_Commands_HashSet extends Predis_MultiBulkCommand { 3133 | public function getCommandId() { return 'HSET'; } 3134 | public function parseResponse($data) { return (bool) $data; } 3135 | } 3136 | 3137 | class Predis_Commands_HashSetPreserve extends Predis_MultiBulkCommand { 3138 | public function getCommandId() { return 'HSETNX'; } 3139 | public function parseResponse($data) { return (bool) $data; } 3140 | } 3141 | 3142 | class Predis_Commands_HashSetMultiple extends Predis_MultiBulkCommand { 3143 | public function getCommandId() { return 'HMSET'; } 3144 | public function filterArguments(Array $arguments) { 3145 | if (count($arguments) === 2 && is_array($arguments[1])) { 3146 | $flattenedKVs = array($arguments[0]); 3147 | $args = &$arguments[1]; 3148 | foreach ($args as $k => $v) { 3149 | $flattenedKVs[] = $k; 3150 | $flattenedKVs[] = $v; 3151 | } 3152 | return $flattenedKVs; 3153 | } 3154 | return $arguments; 3155 | } 3156 | } 3157 | 3158 | class Predis_Commands_HashIncrementBy extends Predis_MultiBulkCommand { 3159 | public function getCommandId() { return 'HINCRBY'; } 3160 | } 3161 | 3162 | class Predis_Commands_HashGet extends Predis_MultiBulkCommand { 3163 | public function getCommandId() { return 'HGET'; } 3164 | } 3165 | 3166 | class Predis_Commands_HashGetMultiple extends Predis_MultiBulkCommand { 3167 | public function getCommandId() { return 'HMGET'; } 3168 | public function filterArguments(Array $arguments) { 3169 | if (count($arguments) === 2 && is_array($arguments[1])) { 3170 | $flattenedKVs = array($arguments[0]); 3171 | $args = &$arguments[1]; 3172 | foreach ($args as $v) { 3173 | $flattenedKVs[] = $v; 3174 | } 3175 | return $flattenedKVs; 3176 | } 3177 | return $arguments; 3178 | } 3179 | } 3180 | 3181 | class Predis_Commands_HashDelete extends Predis_MultiBulkCommand { 3182 | public function getCommandId() { return 'HDEL'; } 3183 | public function parseResponse($data) { return (bool) $data; } 3184 | } 3185 | 3186 | class Predis_Commands_HashExists extends Predis_MultiBulkCommand { 3187 | public function getCommandId() { return 'HEXISTS'; } 3188 | public function parseResponse($data) { return (bool) $data; } 3189 | } 3190 | 3191 | class Predis_Commands_HashLength extends Predis_MultiBulkCommand { 3192 | public function getCommandId() { return 'HLEN'; } 3193 | } 3194 | 3195 | class Predis_Commands_HashKeys extends Predis_MultiBulkCommand { 3196 | public function getCommandId() { return 'HKEYS'; } 3197 | } 3198 | 3199 | class Predis_Commands_HashValues extends Predis_MultiBulkCommand { 3200 | public function getCommandId() { return 'HVALS'; } 3201 | } 3202 | 3203 | class Predis_Commands_HashGetAll extends Predis_MultiBulkCommand { 3204 | public function getCommandId() { return 'HGETALL'; } 3205 | public function parseResponse($data) { 3206 | if ($data instanceof Iterator) { 3207 | return new Predis_Shared_MultiBulkResponseKVIterator($data); 3208 | } 3209 | $result = array(); 3210 | for ($i = 0; $i < count($data); $i++) { 3211 | $result[$data[$i]] = $data[++$i]; 3212 | } 3213 | return $result; 3214 | } 3215 | } 3216 | 3217 | /* multiple databases handling commands */ 3218 | class Predis_Commands_SelectDatabase extends Predis_MultiBulkCommand { 3219 | public function canBeHashed() { return false; } 3220 | public function getCommandId() { return 'SELECT'; } 3221 | } 3222 | 3223 | class Predis_Commands_MoveKey extends Predis_MultiBulkCommand { 3224 | public function canBeHashed() { return false; } 3225 | public function getCommandId() { return 'MOVE'; } 3226 | public function parseResponse($data) { return (bool) $data; } 3227 | } 3228 | 3229 | class Predis_Commands_FlushDatabase extends Predis_MultiBulkCommand { 3230 | public function canBeHashed() { return false; } 3231 | public function getCommandId() { return 'FLUSHDB'; } 3232 | } 3233 | 3234 | class Predis_Commands_FlushAll extends Predis_MultiBulkCommand { 3235 | public function canBeHashed() { return false; } 3236 | public function getCommandId() { return 'FLUSHALL'; } 3237 | } 3238 | 3239 | /* sorting */ 3240 | class Predis_Commands_Sort extends Predis_MultiBulkCommand { 3241 | public function getCommandId() { return 'SORT'; } 3242 | public function filterArguments(Array $arguments) { 3243 | if (count($arguments) === 1) { 3244 | return $arguments; 3245 | } 3246 | 3247 | $query = array($arguments[0]); 3248 | $sortParams = array_change_key_case($arguments[1], CASE_UPPER); 3249 | 3250 | if (isset($sortParams['BY'])) { 3251 | $query[] = 'BY'; 3252 | $query[] = $sortParams['BY']; 3253 | } 3254 | if (isset($sortParams['GET'])) { 3255 | $getargs = $sortParams['GET']; 3256 | if (is_array($getargs)) { 3257 | foreach ($getargs as $getarg) { 3258 | $query[] = 'GET'; 3259 | $query[] = $getarg; 3260 | } 3261 | } 3262 | else { 3263 | $query[] = 'GET'; 3264 | $query[] = $getargs; 3265 | } 3266 | } 3267 | if (isset($sortParams['LIMIT']) && is_array($sortParams['LIMIT']) 3268 | && count($sortParams['LIMIT']) == 2) { 3269 | 3270 | $query[] = 'LIMIT'; 3271 | $query[] = $sortParams['LIMIT'][0]; 3272 | $query[] = $sortParams['LIMIT'][1]; 3273 | } 3274 | if (isset($sortParams['SORT'])) { 3275 | $query[] = strtoupper($sortParams['SORT']); 3276 | } 3277 | if (isset($sortParams['ALPHA']) && $sortParams['ALPHA'] == true) { 3278 | $query[] = 'ALPHA'; 3279 | } 3280 | if (isset($sortParams['STORE'])) { 3281 | $query[] = 'STORE'; 3282 | $query[] = $sortParams['STORE']; 3283 | } 3284 | 3285 | return $query; 3286 | } 3287 | } 3288 | 3289 | /* transactions */ 3290 | class Predis_Commands_Multi extends Predis_MultiBulkCommand { 3291 | public function canBeHashed() { return false; } 3292 | public function getCommandId() { return 'MULTI'; } 3293 | } 3294 | 3295 | class Predis_Commands_Exec extends Predis_MultiBulkCommand { 3296 | public function canBeHashed() { return false; } 3297 | public function getCommandId() { return 'EXEC'; } 3298 | } 3299 | 3300 | class Predis_Commands_Discard extends Predis_MultiBulkCommand { 3301 | public function canBeHashed() { return false; } 3302 | public function getCommandId() { return 'DISCARD'; } 3303 | } 3304 | 3305 | /* publish/subscribe */ 3306 | class Predis_Commands_Subscribe extends Predis_MultiBulkCommand { 3307 | public function canBeHashed() { return false; } 3308 | public function getCommandId() { return 'SUBSCRIBE'; } 3309 | public function filterArguments(Array $arguments) { 3310 | return Predis_Shared_Utils::filterArrayArguments($arguments); 3311 | } 3312 | } 3313 | 3314 | class Predis_Commands_Unsubscribe extends Predis_MultiBulkCommand { 3315 | public function canBeHashed() { return false; } 3316 | public function getCommandId() { return 'UNSUBSCRIBE'; } 3317 | } 3318 | 3319 | class Predis_Commands_SubscribeByPattern extends Predis_MultiBulkCommand { 3320 | public function canBeHashed() { return false; } 3321 | public function getCommandId() { return 'UNSUBSCRIBE'; } 3322 | public function filterArguments(Array $arguments) { 3323 | return Predis_Shared_Utils::filterArrayArguments($arguments); 3324 | } 3325 | } 3326 | 3327 | class Predis_Commands_UnsubscribeByPattern extends Predis_MultiBulkCommand { 3328 | public function canBeHashed() { return false; } 3329 | public function getCommandId() { return 'PUNSUBSCRIBE'; } 3330 | } 3331 | 3332 | class Predis_Commands_Publish extends Predis_MultiBulkCommand { 3333 | public function canBeHashed() { return false; } 3334 | public function getCommandId() { return 'PUBLISH'; } 3335 | } 3336 | 3337 | class Predis_Commands_Watch extends Predis_MultiBulkCommand { 3338 | public function canBeHashed() { return false; } 3339 | public function getCommandId() { return 'WATCH'; } 3340 | public function filterArguments(Array $arguments) { 3341 | if (isset($arguments[0]) && is_array($arguments[0])) { 3342 | return $arguments[0]; 3343 | } 3344 | return $arguments; 3345 | } 3346 | public function parseResponse($data) { return (bool) $data; } 3347 | } 3348 | 3349 | class Predis_Commands_Unwatch extends Predis_MultiBulkCommand { 3350 | public function canBeHashed() { return false; } 3351 | public function getCommandId() { return 'UNWATCH'; } 3352 | public function parseResponse($data) { return (bool) $data; } 3353 | } 3354 | 3355 | /* persistence control commands */ 3356 | class Predis_Commands_Save extends Predis_MultiBulkCommand { 3357 | public function canBeHashed() { return false; } 3358 | public function getCommandId() { return 'SAVE'; } 3359 | } 3360 | 3361 | class Predis_Commands_BackgroundSave extends Predis_MultiBulkCommand { 3362 | public function canBeHashed() { return false; } 3363 | public function getCommandId() { return 'BGSAVE'; } 3364 | public function parseResponse($data) { 3365 | if ($data == 'Background saving started') { 3366 | return true; 3367 | } 3368 | return $data; 3369 | } 3370 | } 3371 | 3372 | class Predis_Commands_BackgroundRewriteAppendOnlyFile extends Predis_MultiBulkCommand { 3373 | public function canBeHashed() { return false; } 3374 | public function getCommandId() { return 'BGREWRITEAOF'; } 3375 | public function parseResponse($data) { 3376 | return $data == 'Background append only file rewriting started'; 3377 | } 3378 | } 3379 | 3380 | class Predis_Commands_LastSave extends Predis_MultiBulkCommand { 3381 | public function canBeHashed() { return false; } 3382 | public function getCommandId() { return 'LASTSAVE'; } 3383 | } 3384 | 3385 | class Predis_Commands_Shutdown extends Predis_MultiBulkCommand { 3386 | public function canBeHashed() { return false; } 3387 | public function getCommandId() { return 'SHUTDOWN'; } 3388 | public function closesConnection() { return true; } 3389 | } 3390 | 3391 | /* remote server control commands */ 3392 | class Predis_Commands_Info extends Predis_MultiBulkCommand { 3393 | public function canBeHashed() { return false; } 3394 | public function getCommandId() { return 'INFO'; } 3395 | public function parseResponse($data) { 3396 | $info = array(); 3397 | $infoLines = explode("\r\n", $data, -1); 3398 | foreach ($infoLines as $row) { 3399 | @list($k, $v) = explode(':', $row); 3400 | if ($row === '' || !isset($v)) { 3401 | continue; 3402 | } 3403 | if (!preg_match('/^db\d+$/', $k)) { 3404 | if ($k === 'allocation_stats') { 3405 | $info[$k] = $this->parseAllocationStats($v); 3406 | continue; 3407 | } 3408 | $info[$k] = $v; 3409 | } 3410 | else { 3411 | $info[$k] = $this->parseDatabaseStats($v); 3412 | } 3413 | } 3414 | return $info; 3415 | } 3416 | protected function parseDatabaseStats($str) { 3417 | $db = array(); 3418 | foreach (explode(',', $str) as $dbvar) { 3419 | list($dbvk, $dbvv) = explode('=', $dbvar); 3420 | $db[trim($dbvk)] = $dbvv; 3421 | } 3422 | return $db; 3423 | } 3424 | protected function parseAllocationStats($str) { 3425 | $stats = array(); 3426 | foreach (explode(',', $str) as $kv) { 3427 | @list($size, $objects, $extra) = explode('=', $kv); 3428 | // hack to prevent incorrect values when parsing the >=256 key 3429 | if (isset($extra)) { 3430 | $size = ">=$objects"; 3431 | $objects = $extra; 3432 | } 3433 | $stats[$size] = $objects; 3434 | } 3435 | return $stats; 3436 | } 3437 | } 3438 | 3439 | class Predis_Commands_Info_v24 extends Predis_Commands_Info { 3440 | public function parseResponse($data) { 3441 | $info = array(); 3442 | $current = null; 3443 | $infoLines = explode("\r\n", $data, -1); 3444 | foreach ($infoLines as $row) { 3445 | if ($row === '') { 3446 | continue; 3447 | } 3448 | if (preg_match('/^# (\w+)$/', $row, $matches)) { 3449 | $info[$matches[1]] = array(); 3450 | $current = &$info[$matches[1]]; 3451 | continue; 3452 | } 3453 | list($k, $v) = explode(':', $row); 3454 | if (!preg_match('/^db\d+$/', $k)) { 3455 | if ($k === 'allocation_stats') { 3456 | $current[$k] = $this->parseAllocationStats($v); 3457 | continue; 3458 | } 3459 | $current[$k] = $v; 3460 | } 3461 | else { 3462 | $current[$k] = $this->parseDatabaseStats($v); 3463 | } 3464 | } 3465 | return $info; 3466 | } 3467 | } 3468 | 3469 | class Predis_Commands_SlaveOf extends Predis_MultiBulkCommand { 3470 | public function canBeHashed() { return false; } 3471 | public function getCommandId() { return 'SLAVEOF'; } 3472 | public function filterArguments(Array $arguments) { 3473 | if (count($arguments) === 0 || $arguments[0] === 'NO ONE') { 3474 | return array('NO', 'ONE'); 3475 | } 3476 | return $arguments; 3477 | } 3478 | } 3479 | 3480 | class Predis_Commands_Config extends Predis_MultiBulkCommand { 3481 | public function canBeHashed() { return false; } 3482 | public function getCommandId() { return 'CONFIG'; } 3483 | } 3484 | ?> -------------------------------------------------------------------------------- /wp-redis-cache/readme.txt: -------------------------------------------------------------------------------- 1 | == Description == 2 | ## Wp Redis Cache 3 | ------ 4 | Cache Wordpress using Redis, the fastest way to date to cache Wordpress. 5 | 6 | Please see [https://github.com/BenjaminAdams/wp-redis-cache](https://github.com/BenjaminAdams/wp-redis-cache) for the latest information and other needed setup files. 7 | 8 | ### Requirements 9 | ------ 10 | * [Wordpress](http://wordpress.org) - CMS framework/blogging system 11 | * [Redis](http://redis.io/) - Key Value in memory caching 12 | * [Predis](https://github.com/nrk/predis) - PHP api for Redis 13 | 14 | == Installation == 15 | Install Redis, must have root access to your machine. On debian it's as simple as: 16 | ```bash 17 | sudo apt-get install redis-server 18 | ``` 19 | On other systems please refer to the [Redis website](http://redis.io/). 20 | 21 | Move the folder wp-redis-cache to the plugin directory and activate the plugin. In the admin section you can set how long you will cache the post for. By default it will cache the post for 12 hours. 22 | Note: This plugin is optional and is used to refresh the cache after you update a post/page 23 | 24 | Move the `index-wp-redis.php` to the root/base Wordpress directory. 25 | 26 | Move the `index.php` to the root/base Wordpress directory. Or manually change the `index.php` to: 27 | 28 | ```php 29 | 32 | ``` 33 | In `index-wp-redis.php` change `$ip_of_your_website` to the IP of your server 34 | 35 | *Note: Sometimes when you upgrade Wordpress it will replace over your `index.php` file and you will have to redo this step. This is the reason we don't just replace the contents of `index-wp-redis.php` with `index.php`. 36 | 37 | We do this because Wordpress is no longer in charge of displaying our posts. Redis will now server the post if it is in the cache. If the post is not in the Redis cache it will then call Wordpress to serve the page and then cache it for the next pageload 38 | 39 | 40 | ### Benchmark 41 | ------ 42 | I welcome you to compare the page load times of this caching system with other popular Caching plugins such as [Wp Super Cache](http://wordpress.org/plugins/wp-super-cache/) and [W3 Total Cache](http://wordpress.org/plugins/w3-total-cache/) 43 | 44 | With a fresh Wordpress install: 45 | 46 | Wp Super Cache 47 | ``` 48 | Page generated in 0.318 seconds. 49 | ``` 50 | 51 | W3 Total Cache 52 | ``` 53 | Page generated in 0.30484 seconds. 54 | ``` 55 | 56 | Wp Redis Cache 57 | ``` 58 | Page generated in 0.00902 seconds. 59 | ``` 60 | 61 | 62 | == Installation == 63 | 64 | == Installation == 65 | ------ 66 | Install Redis, must have root access to your machine. On debian it's as simple as: 67 | ```bash 68 | sudo apt-get install redis-server 69 | ``` 70 | On other systems please refer to the [Redis website](http://redis.io/). 71 | 72 | Move the folder wp-redis-cache to the plugin directory and activate the plugin. In the admin section you can set how long you will cache the post for. By default it will cache the post for 12 hours. 73 | Note: This plugin is optional and is used to refresh the cache after you update a post/page 74 | 75 | Move the `index-wp-redis.php` to the root/base Wordpress directory. 76 | 77 | Move the `index.php` to the root/base Wordpress directory. Or manually change the `index.php` to: 78 | 79 | ```php 80 | 83 | ``` 84 | In `index-wp-redis.php` change `$ip_of_your_website` to the IP of your server 85 | 86 | *Note: Sometimes when you upgrade Wordpress it will replace over your `index.php` file and you will have to redo this step. This is the reason we don't just replace the contents of `index-wp-redis.php` with `index.php`. 87 | 88 | We do this because Wordpress is no longer in charge of displaying our posts. Redis will now server the post if it is in the cache. If the post is not in the Redis cache it will then call Wordpress to serve the page and then cache it for the next pageload 89 | --------------------------------------------------------------------------------