├── .gitignore ├── README.md ├── LiteMemcache.class.php └── LiteMemcache.test.php /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiteMemcache ![English version](http://upload.wikimedia.org/wikipedia/en/thumb/a/ae/Flag_of_the_United_Kingdom.svg/22px-Flag_of_the_United_Kingdom.svg.png) 2 | 3 | *LiteMemcache is the most lightweight Memcached client written in PHP* 4 | 5 | **Key points** 6 | 7 | * *Full-featured:* supports all Memcached commands (including CAS) 8 | * *Simple:* just Memcached protocol, nothing more 9 | * *Really tiny:* only 105 lines of code 10 | * *Requires nothing:* pure PHP implementation 11 | * *Reliable:* all methods are covered with unit-tests 12 | 13 | **Usage example** 14 | 15 | ```php 16 | $client = new LiteMemcache( 'host:port' ); 17 | $client->set( 'key', 'value' ); 18 | $value = $client->get( 'key' ); 19 | ``` 20 | 21 | -------------------------------------------------- 22 | 23 | # LiteMemcache ![Русская версия](http://upload.wikimedia.org/wikipedia/en/thumb/f/f3/Flag_of_Russia.svg/22px-Flag_of_Russia.svg.png) 24 | 25 | *LiteMemcache - самый легковесный клиент для Memcached, написанный на PHP* 26 | 27 | **Основные моменты** 28 | 29 | * *Полнофункциональный:* поддерживает все команды Memcached (включая CAS) 30 | * *Простой:* только протокол Memcached, ничего лишнего 31 | * *Крошечный:* всего 105 строк кода 32 | * *Нетребовательный:* написан на чистом PHP 33 | * *Надежный:* все методы покрыты юнит-тестами 34 | 35 | **Пример использования** 36 | 37 | ```php 38 | $client = new LiteMemcache( 'хост:порт' ); 39 | $client->set( 'ключ', 'значение' ); 40 | $value = $client->get( 'ключ' ); 41 | ``` 42 | 43 | -------------------------------------------------- 44 | 45 | Keywords: litememcache, memcached, memcache, php, pure, client, protocol, lightweight, simple -------------------------------------------------------------------------------- /LiteMemcache.class.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class LiteMemcache 10 | { 11 | private $_socket, $_replies, $_lastReply; 12 | 13 | public function __construct( $server ) 14 | { 15 | $this->_socket = stream_socket_client( $server, $errno, $errstr ); 16 | if ( !$this->_socket ) 17 | { 18 | throw new Exception( "$errstr ($errno)" ); 19 | } 20 | $this->_replies = array( 21 | 'STORED' => true, 22 | 'NOT_STORED' => false, 23 | 'EXISTS' => false, 24 | 'OK' => true, 25 | 'ERROR' => false, 26 | 'DELETED' => true, 27 | 'NOT_FOUND' => false, 28 | 'ERROR' => null, 29 | 'CLIENT_ERROR' => null, 30 | 'SERVER_ERROR' => null ); 31 | } 32 | 33 | public function add( $key, $value, $exptime = 0, $flags = 0 ) 34 | { 35 | return $this->query( array( "add $key $flags $exptime " . strlen( $value ), $value ) ); 36 | } 37 | 38 | public function append( $key, $value ) 39 | { 40 | return $this->query( array( "append $key 0 0 " . strlen( $value ), $value ) ); 41 | } 42 | 43 | public function cas( $key, $value, $cas, $exptime = 0, $flags = 0 ) 44 | { 45 | return $this->query( array( "cas $key $flags $exptime " . strlen( $value ) . " $cas", $value ) ); 46 | } 47 | 48 | public function decr( $key, $value = 1 ) 49 | { 50 | return $this->query( "decr $key $value" ); 51 | } 52 | 53 | public function del( $key ) 54 | { 55 | return $this->query( "delete $key" ); 56 | } 57 | 58 | public function flushAll( $exptime = 0 ) 59 | { 60 | return $this->query( "flush_all $exptime" ); 61 | } 62 | 63 | public function get( $key, $ext = false ) 64 | { 65 | $keys = array_fill_keys( ( array ) $key, 66 | $ext ? array( 'value' => null, 'flags' => null, 'cas' => null ) : null ); 67 | $words = $this->query( ( $ext ? 'gets' : 'get' ) . ' ' . implode( ' ', array_keys( $keys ) ) ); 68 | while ( $words[ 0 ] == 'VALUE' ) 69 | { 70 | $value = fread( $this->_socket, $words[ 3 ] + 2 ); 71 | $keys[ $words[ 1 ] ] = $ext ? array( 72 | 'value' => substr( $value, 0, strlen( $value ) - 2 ), 73 | 'flags' => $words[ 2 ], 74 | 'cas' => $words[ 4 ] ) : substr( $value, 0, strlen( $value ) - 2 ); 75 | $words = $this->_readLine(); 76 | } 77 | return is_array( $key ) ? $keys : reset( $keys ); 78 | } 79 | 80 | public function getLastReply() 81 | { 82 | return $this->_lastReply; 83 | } 84 | 85 | public function incr( $key, $value = 1 ) 86 | { 87 | return $this->query( "incr $key $value" ); 88 | } 89 | 90 | public function prepend( $key, $value ) 91 | { 92 | return $this->query( array( "prepend $key 0 0 " . strlen( $value ), $value ) ); 93 | } 94 | 95 | public function query( $query ) 96 | { 97 | $query = is_array( $query ) ? implode( "\r\n", $query ) : $query; 98 | fwrite( $this->_socket, $query . "\r\n" ); 99 | return $this->_readLine(); 100 | } 101 | 102 | public function replace( $key, $value, $exptime = 0, $flags = 0 ) 103 | { 104 | return $this->query( array( "replace $key $flags $exptime " . strlen( $value ), $value ) ); 105 | } 106 | 107 | public function set( $key, $value, $exptime = 0, $flags = 0 ) 108 | { 109 | return $this->query( array( "set $key $flags $exptime " . strlen( $value ), $value ) ); 110 | } 111 | 112 | private function _readLine() 113 | { 114 | $line = fgets( $this->_socket ); 115 | $this->_lastReply = substr( $line, 0, strlen( $line ) - 2 ); 116 | $words = explode( ' ', $this->_lastReply ); 117 | $result = isset( $this->_replies[ $words[ 0 ] ] ) ? $this->_replies[ $words[ 0 ] ] : $words; 118 | if ( is_null( $result ) ) 119 | { 120 | throw new Exception( $this->_lastReply ); 121 | } 122 | return ( is_array( $result ) && count( $result ) == 1 ) ? reset( $result ) : $result; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /LiteMemcache.test.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | require_once ( 'LiteMemcache.class.php' ); 13 | 14 | class LiteMemcacheTest extends PHPUnit_Framework_TestCase 15 | { 16 | public function __construct( $name = NULL, array $data = array(), $dataName = '' ) 17 | { 18 | parent::__construct( $name, $data, $dataName ); 19 | $this->_client = new LiteMemcache( 'localhost:11211' ); 20 | } 21 | 22 | public function testClassName() 23 | { 24 | $this->assertSame( 'LiteMemcache', get_class( $this->_client ) ); 25 | } 26 | 27 | public function providerSetDifferentDataTypes() 28 | { 29 | $data = array(); 30 | $data[] = array( $this->_client, 'key', 'string', 'string' ); 31 | $data[] = array( $this->_client, 'key', 1, '1' ); 32 | $data[] = array( $this->_client, 'key', 0, '0' ); 33 | $data[] = array( $this->_client, 'key', 3.14, '3.14' ); 34 | $data[] = array( $this->_client, 'key', 0.0, '0' ); 35 | $data[] = array( $this->_client, 'key', true, '1' ); 36 | $data[] = array( $this->_client, 'key', false, '' ); 37 | $data[] = array( $this->_client, 'key', null, '' ); 38 | return $data; 39 | } 40 | 41 | /** 42 | * @dataProvider providerSetDifferentDataTypes 43 | */ 44 | public function testSetDifferentDataTypes( $client, $key, $set, $get ) 45 | { 46 | $this->assertSame( true, $client->set( $key, $set ) ); 47 | $this->assertSame( 'STORED', $client->getLastReply() ); 48 | $this->assertSame( $get, $client->get( $key ) ); 49 | } 50 | 51 | public function testSeqSetGet() 52 | { 53 | $client = $this->_client; 54 | $this->assertSame( true, $client->set( 'key', 'value1' ) ); 55 | $this->assertSame( 'STORED', $client->getLastReply() ); 56 | $this->assertSame( 'value1', $client->get( 'key' ) ); 57 | $this->assertSame( true, $client->set( 'key', 'value2' ) ); 58 | $this->assertSame( 'STORED', $client->getLastReply() ); 59 | $this->assertSame( 'value2', $client->get( 'key' ) ); 60 | } 61 | 62 | public function testMultipleGet() 63 | { 64 | $client = $this->_client; 65 | $this->assertSame( true, $client->set( 'key1', 'value1' ) ); 66 | $this->assertSame( 'STORED', $client->getLastReply() ); 67 | $this->assertSame( true, $client->set( 'key2', 'value2' ) ); 68 | $this->assertSame( 'STORED', $client->getLastReply() ); 69 | $this->assertSame( 'value1', $client->get( 'key1' ) ); 70 | $this->assertSame( 'value2', $client->get( 'key2' ) ); 71 | $this->assertSame( array( 'key2' => 'value2' ), $client->get( array( 'key2' ) ) ); 72 | $this->assertSame( null, $client->get( 'key3' ) ); 73 | $this->assertSame( array( 'key1' => 'value1', 'key2' => 'value2', 'key3' => null ), 74 | $client->get( array( 'key1', 'key2', 'key3' ) ) ); 75 | $this->assertSame( array( 'key1' => 'value1', 'key3' => null, 'key2' => 'value2' ), 76 | $client->get( array( 'key1', 'key3', 'key2' ) ) ); 77 | $this->assertSame( array( 'key3' => null, 'key1' => 'value1', 'key2' => 'value2' ), 78 | $client->get( array( 'key3', 'key1', 'key2' ) ) ); 79 | } 80 | 81 | public function testWrongKey() 82 | { 83 | $client = $this->_client; 84 | $this->assertSame( null, $client->get( 'wrong-key' ) ); 85 | } 86 | 87 | public function testExpiredKey() 88 | { 89 | $client = $this->_client; 90 | $this->assertSame( true, $client->set( 'key', 'value', 1 ) ); 91 | $this->assertSame( 'STORED', $client->getLastReply() ); 92 | $this->assertSame( 'value', $client->get( 'key' ) ); 93 | usleep( 1100000 ); 94 | $this->assertSame( null, $client->get( 'key' ) ); 95 | } 96 | 97 | public function testClientError() 98 | { 99 | try 100 | { 101 | $this->_client->get( 'key with spaces' ); 102 | $this->assertTrue( false ); 103 | } 104 | catch ( Exception $ex ) 105 | { 106 | $this->assertTrue( true ); 107 | } 108 | } 109 | 110 | public function testDeleteKey() 111 | { 112 | $client = $this->_client; 113 | $this->assertSame( true, $client->set( 'key', 'value' ) ); 114 | $this->assertSame( 'STORED', $client->getLastReply() ); 115 | $this->assertSame( 'value', $client->get( 'key' ) ); 116 | $this->assertSame( true, $client->del( 'key' ) ); 117 | $this->assertSame( 'DELETED', $client->getLastReply() ); 118 | $this->assertSame( null, $client->get( 'key' ) ); 119 | $this->assertSame( false, $client->del( 'key' ) ); 120 | $this->assertSame( 'NOT_FOUND', $client->getLastReply() ); 121 | } 122 | 123 | public function testAppend() 124 | { 125 | $client = $this->_client; 126 | $client->del( 'key' ); 127 | $this->assertSame( null, $client->get( 'key' ) ); 128 | $this->assertSame( false, $client->append( 'key', 'value' ) ); 129 | $this->assertSame( 'NOT_STORED', $client->getLastReply() ); 130 | $this->assertSame( null, $client->get( 'key' ) ); 131 | $this->assertSame( true, $client->set( 'key', 'hello' ) ); 132 | $this->assertSame( 'STORED', $client->getLastReply() ); 133 | $this->assertSame( 'hello', $client->get( 'key' ) ); 134 | $this->assertSame( true, $client->append( 'key', ' world' ) ); 135 | $this->assertSame( 'STORED', $client->getLastReply() ); 136 | $this->assertSame( 'hello world', $client->get( 'key' ) ); 137 | } 138 | 139 | public function testPrepend() 140 | { 141 | $client = $this->_client; 142 | $client->del( 'key' ); 143 | $this->assertSame( null, $client->get( 'key' ) ); 144 | $this->assertSame( false, $client->prepend( 'key', 'value' ) ); 145 | $this->assertSame( 'NOT_STORED', $client->getLastReply() ); 146 | $this->assertSame( null, $client->get( 'key' ) ); 147 | $this->assertSame( true, $client->set( 'key', 'world' ) ); 148 | $this->assertSame( 'STORED', $client->getLastReply() ); 149 | $this->assertSame( 'world', $client->get( 'key' ) ); 150 | $this->assertSame( true, $client->prepend( 'key', 'hello ' ) ); 151 | $this->assertSame( 'STORED', $client->getLastReply() ); 152 | $this->assertSame( 'hello world', $client->get( 'key' ) ); 153 | } 154 | 155 | public function testAdd() 156 | { 157 | $client = $this->_client; 158 | $client->del( 'key' ); 159 | $this->assertSame( null, $client->get( 'key' ) ); 160 | $this->assertSame( true, $client->add( 'key', 'value1' ) ); 161 | $this->assertSame( 'STORED', $client->getLastReply() ); 162 | $this->assertSame( 'value1', $client->get( 'key' ) ); 163 | $this->assertSame( false, $client->add( 'key', 'value2' ) ); 164 | $this->assertSame( 'NOT_STORED', $client->getLastReply() ); 165 | $this->assertSame( 'value1', $client->get( 'key' ) ); 166 | } 167 | 168 | public function testReplace() 169 | { 170 | $client = $this->_client; 171 | $client->del( 'key' ); 172 | $this->assertSame( null, $client->get( 'key' ) ); 173 | $this->assertSame( false, $client->replace( 'key', 'value1' ) ); 174 | $this->assertSame( 'NOT_STORED', $client->getLastReply() ); 175 | $this->assertSame( null, $client->get( 'key' ) ); 176 | $this->assertSame( true, $client->set( 'key', 'value2' ) ); 177 | $this->assertSame( 'STORED', $client->getLastReply() ); 178 | $this->assertSame( 'value2', $client->get( 'key' ) ); 179 | $this->assertSame( true, $client->replace( 'key', 'value3' ) ); 180 | $this->assertSame( 'STORED', $client->getLastReply() ); 181 | $this->assertSame( 'value3', $client->get( 'key' ) ); 182 | } 183 | 184 | public function testIncr() 185 | { 186 | $client = $this->_client; 187 | $client->del( 'key' ); 188 | $this->assertSame( null, $client->get( 'key' ) ); 189 | $this->assertSame( false, $client->incr( 'key' ) ); 190 | $this->assertSame( 'NOT_FOUND', $client->getLastReply() ); 191 | $this->assertSame( null, $client->get( 'key' ) ); 192 | $this->assertSame( true, $client->set( 'key', 1 ) ); 193 | $this->assertSame( 'STORED', $client->getLastReply() ); 194 | $this->assertSame( '1', $client->get( 'key' ) ); 195 | $this->assertSame( '2', $client->incr( 'key' ) ); 196 | $this->assertSame( '2', $client->get( 'key' ) ); 197 | } 198 | 199 | public function testDecr() 200 | { 201 | $client = $this->_client; 202 | $client->del( 'key' ); 203 | $this->assertSame( null, $client->get( 'key' ) ); 204 | $this->assertSame( false, $client->decr( 'key' ) ); 205 | $this->assertSame( 'NOT_FOUND', $client->getLastReply() ); 206 | $this->assertSame( null, $client->get( 'key' ) ); 207 | $this->assertSame( true, $client->set( 'key', 2 ) ); 208 | $this->assertSame( 'STORED', $client->getLastReply() ); 209 | $this->assertSame( '2', $client->get( 'key' ) ); 210 | $this->assertSame( '1', $client->decr( 'key' ) ); 211 | $this->assertSame( '1', $client->get( 'key' ) ); 212 | } 213 | 214 | public function testFlushAll() 215 | { 216 | $client = $this->_client; 217 | $this->assertSame( true, $client->set( 'key1', 'value1' ) ); 218 | $this->assertSame( 'STORED', $client->getLastReply() ); 219 | $this->assertSame( true, $client->set( 'key2', 'value2' ) ); 220 | $this->assertSame( 'STORED', $client->getLastReply() ); 221 | $this->assertSame( 'value1', $client->get( 'key1' ) ); 222 | $this->assertSame( 'value2', $client->get( 'key2' ) ); 223 | $this->assertSame( true, $client->flushAll() ); 224 | $this->assertSame( 'OK', $client->getLastReply() ); 225 | } 226 | 227 | public function testFlags() 228 | { 229 | $client = $this->_client; 230 | $client->del( 'key' ); 231 | $this->assertSame( true, $client->set( 'key1', 'value1', 0, 1 ) ); 232 | $this->assertSame( 'STORED', $client->getLastReply() ); 233 | $result = $client->get( 'key1', true ); 234 | $this->assertSame( 'value1', $result[ 'value' ] ); 235 | $this->assertSame( '1', $result[ 'flags' ] ); 236 | $this->assertTrue( is_numeric( $result[ 'cas' ] ) ); 237 | $this->assertSame( true, $client->set( 'key2', 'value2', 0, 2 ) ); 238 | $this->assertSame( 'STORED', $client->getLastReply() ); 239 | $result = $client->get( array( 'key1', 'key2' ), true ); 240 | $this->assertSame( 'value1', $result[ 'key1' ][ 'value' ] ); 241 | $this->assertSame( '1', $result[ 'key1' ][ 'flags' ] ); 242 | $this->assertTrue( is_numeric( $result[ 'key1' ][ 'cas' ] ) ); 243 | $this->assertSame( 'value2', $result[ 'key2' ][ 'value' ] ); 244 | $this->assertSame( '2', $result[ 'key2' ][ 'flags' ] ); 245 | $this->assertTrue( is_numeric( $result[ 'key2' ][ 'cas' ] ) ); 246 | } 247 | 248 | public function testCas() 249 | { 250 | $client = $this->_client; 251 | $client->del( 'key' ); 252 | $this->assertSame( true, $client->set( 'key', 'value1' ) ); 253 | $this->assertSame( 'STORED', $client->getLastReply() ); 254 | $result = $client->get( 'key', true ); 255 | $this->assertSame( 'value1', $result[ 'value' ] ); 256 | $this->assertSame( true, $client->cas( 'key', 'value2', $result[ 'cas' ] ) ); 257 | $this->assertSame( 'STORED', $client->getLastReply() ); 258 | $this->assertSame( false, $client->cas( 'key', 'value3', $result[ 'cas' ] ) ); 259 | $this->assertSame( 'EXISTS', $client->getLastReply() ); 260 | $this->assertSame( 'value2', $client->get( 'key' ) ); 261 | $client->del( 'key' ); 262 | $this->assertSame( false, $client->cas( 'key', 'value4', $result[ 'cas' ] ) ); 263 | $this->assertSame( 'NOT_FOUND', $client->getLastReply() ); 264 | $this->assertSame( null, $client->get( 'key' ) ); 265 | } 266 | } --------------------------------------------------------------------------------