nokitakaze /
php-keyvalue
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | namespace NokitaKaze\KeyValue; |
||
| 4 | |||
| 5 | use NokitaKaze\Mutex\FileMutex; |
||
| 6 | |||
| 7 | /** |
||
| 8 | * Class Key-value хранилище, использующее Redis |
||
| 9 | */ |
||
| 10 | class RedisStorage extends AbstractStorage { |
||
| 11 | const REDIS_RECV_CHUNK_SIZE = 4096; |
||
| 12 | const REDIS_TIMEOUT_LIMIT = 30; |
||
| 13 | const KeyPrefix = 'AscetKeyValue'; |
||
| 14 | const REDIS_STRING_CHUNK_SIZE = 30 * 1024; |
||
| 15 | |||
| 16 | /** |
||
| 17 | * @var resource|null |
||
| 18 | */ |
||
| 19 | protected $_socket = null; |
||
| 20 | const ERROR_CODE = 400; |
||
| 21 | |||
| 22 | protected $_mutex_root_folder = null; |
||
| 23 | |||
| 24 | /** |
||
| 25 | * @param KeyValueSettings|object $settings |
||
| 26 | */ |
||
| 27 | 265 | function __construct($settings) { |
|
| 28 | $default_settings = [ |
||
| 29 | 265 | 'database' => 0, |
|
| 30 | 265 | 'storage_type' => self::StoragePersistent, |
|
| 31 | 265 | 'host' => '127.0.0.1', |
|
| 32 | 265 | 'port' => 6379, |
|
| 33 | 265 | 'timeout' => self::REDIS_TIMEOUT_LIMIT, |
|
| 34 | 265 | 'string_chunk_size' => self::REDIS_STRING_CHUNK_SIZE, |
|
| 35 | 53 | ]; |
|
| 36 | 265 | foreach ($default_settings as $key => &$default_value) { |
|
| 37 | 265 | if (!isset($settings->{$key})) { |
|
| 38 | 265 | $settings->{$key} = $default_value; |
|
| 39 | 53 | } |
|
| 40 | 53 | } |
|
| 41 | |||
| 42 | 265 | $this->settings = $settings; |
|
| 43 | 265 | $this->standard_prefix_strategy(); |
|
| 44 | 265 | if (isset($settings->folder)) { |
|
| 45 | 5 | $this->_mutex_root_folder = $settings->folder; |
|
| 46 | 1 | } |
|
| 47 | 265 | } |
|
| 48 | |||
| 49 | 265 | function __destruct() { |
|
| 50 | 265 | if (!is_null($this->_socket)) { |
|
| 51 | 105 | if (@socket_shutdown($this->_socket)) { |
|
| 52 | 105 | @socket_close($this->_socket); |
|
|
0 ignored issues
–
show
|
|||
| 53 | 21 | } |
|
| 54 | 21 | } |
|
| 55 | 265 | } |
|
| 56 | |||
| 57 | /** |
||
| 58 | * @throws KeyValueException |
||
| 59 | */ |
||
| 60 | 110 | protected function lazy_init() { |
|
| 61 | 110 | if (!is_null($this->_socket)) { |
|
| 62 | 100 | return; |
|
| 63 | } |
||
| 64 | 110 | $this->_socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
|
| 65 | 110 | if ($this->_socket === false) { |
|
| 66 | // @codeCoverageIgnoreStart |
||
| 67 | throw new KeyValueException('Can not create socket', self::ERROR_CODE + 0); |
||
| 68 | // @codeCoverageIgnoreEnd |
||
| 69 | } |
||
| 70 | 110 | $u = @socket_connect($this->_socket, $this->settings->host, $this->settings->port); |
|
| 71 | 110 | if (!$u) { |
|
| 72 | 5 | throw new KeyValueException('Can not connect to '.$this->settings->host.':'.$this->settings->port, |
|
| 73 | 5 | self::ERROR_CODE + 1); |
|
| 74 | } |
||
| 75 | 110 | if ($this->settings->database != 0) { |
|
| 76 | 5 | $this->redis_send(sprintf('select %d', $this->settings->database)); |
|
| 77 | 5 | $redis_answer = $this->redis_recv(1); |
|
| 78 | 5 | if ($redis_answer != "+OK\r\n") { |
|
| 79 | 5 | throw new KeyValueException(self::format_error('Can not select database '.$this->settings->database, |
|
| 80 | 5 | $redis_answer), self::ERROR_CODE + 3); |
|
| 81 | } |
||
| 82 | 1 | } |
|
| 83 | 110 | } |
|
| 84 | |||
| 85 | /** |
||
| 86 | * @param string $command |
||
| 87 | * |
||
| 88 | * @throws KeyValueException |
||
| 89 | */ |
||
| 90 | 110 | function redis_send($command) { |
|
| 91 | 110 | $this->lazy_init(); |
|
| 92 | 110 | $command .= "\n"; |
|
| 93 | 110 | $i = @socket_write($this->_socket, $command, strlen($command)); |
|
| 94 | 110 | if (($i === false) or ($i != strlen($command))) { |
|
| 95 | 5 | throw new KeyValueException('Can not send command', self::ERROR_CODE + 2); |
|
| 96 | } |
||
| 97 | 105 | } |
|
| 98 | |||
| 99 | /** |
||
| 100 | * Получаем данные из соккета |
||
| 101 | * |
||
| 102 | * Этот метод не делает lazy_init, потому что его делает redis_send, а redis_recv обязан (must) быть вызван |
||
| 103 | * только после redis_send |
||
| 104 | * |
||
| 105 | * @param integer $mode (0 — набрать на блок; 1 — до crlf; 2 — получить как можно быстрее) |
||
| 106 | * |
||
| 107 | * @return string |
||
| 108 | * @throws KeyValueException |
||
| 109 | */ |
||
| 110 | 105 | protected function redis_recv($mode = 1) { |
|
| 111 | 105 | $buf = ''; |
|
| 112 | 105 | $r = [$this->_socket]; |
|
| 113 | 105 | $o = null; |
|
| 114 | do { |
||
| 115 | 105 | $count = @socket_select($r, $o, $o, (($mode !== 2) or ($buf == '')) ? $this->settings->timeout : 0); |
|
| 116 | 105 | if ($count === false) { |
|
| 117 | 5 | throw new KeyValueException('Redis server read error', self::ERROR_CODE + 16); |
|
| 118 | 105 | } elseif (($count === 0) and ($mode !== 2)) { |
|
| 119 | 5 | throw new KeyValueException('Redis server timeout', self::ERROR_CODE + 10); |
|
| 120 | 105 | } elseif ($count === 0) { |
|
| 121 | 10 | return $buf; |
|
| 122 | } |
||
| 123 | 105 | $i = @socket_recv($this->_socket, $buf_chunk, self::REDIS_RECV_CHUNK_SIZE, 0); |
|
| 124 | 105 | if ($i === false) { |
|
| 125 | // @codeCoverageIgnoreStart |
||
| 126 | throw new KeyValueException('Can not read from redis server', self::ERROR_CODE + 4); |
||
| 127 | // @codeCoverageIgnoreEnd |
||
| 128 | } |
||
| 129 | 105 | $buf .= $buf_chunk; |
|
| 130 | 105 | if (($mode === 1) and (strpos($buf, "\r\n") !== false)) { |
|
| 131 | 105 | return $buf; |
|
| 132 | } |
||
| 133 | 15 | } while ((($mode === 0) and (strlen($buf) < self::REDIS_RECV_CHUNK_SIZE)) or in_array($mode, [1, 2])); |
|
| 134 | |||
| 135 | 5 | return $buf; |
|
| 136 | } |
||
| 137 | |||
| 138 | /** |
||
| 139 | * @param string $key |
||
| 140 | * |
||
| 141 | * @return string |
||
| 142 | */ |
||
| 143 | 95 | protected function get_full_key_name($key) { |
|
| 144 | 95 | return sprintf('%s:%s:%s', self::KeyPrefix, $this->_prefix, $key); |
|
| 145 | } |
||
| 146 | |||
| 147 | /** |
||
| 148 | * @param string $key |
||
| 149 | * |
||
| 150 | * @return object|null |
||
| 151 | */ |
||
| 152 | 50 | protected function get_value_full_clear($key) { |
|
| 153 | try { |
||
| 154 | 50 | $this->get_lock($key); |
|
| 155 | 50 | $value = $this->get_value_full_clear_low_level($key); |
|
| 156 | 45 | $this->release_lock(); |
|
| 157 | |||
| 158 | 45 | return $value; |
|
| 159 | 5 | } catch (KeyValueException $e) { |
|
| 160 | 5 | $this->release_lock(); |
|
| 161 | |||
| 162 | 5 | return null; |
|
| 163 | } |
||
| 164 | } |
||
| 165 | |||
| 166 | /** |
||
| 167 | * @param string $key |
||
| 168 | * |
||
| 169 | * @return object|null |
||
| 170 | * @throws KeyValueException |
||
| 171 | */ |
||
| 172 | 50 | protected function get_value_full_clear_low_level($key) { |
|
| 173 | 50 | $this->redis_send(sprintf('hgetall %s', $this->get_full_key_name($key))); |
|
| 174 | // @todo -LOADING Redis is loading the dataset in memory |
||
| 175 | 50 | $recv_text = $this->redis_recv(1); |
|
| 176 | 50 | if ($recv_text == "*0\r\n") { |
|
| 177 | 40 | return null; |
|
| 178 | } |
||
| 179 | 45 | if (substr($recv_text, 0, 1) != '*') { |
|
| 180 | 5 | throw new KeyValueException(self::format_error('Redis sent malformed response', $recv_text), |
|
| 181 | 5 | self::ERROR_CODE + 11); |
|
| 182 | } |
||
| 183 | 45 | list($num, $strings) = $this->get_hget_from_redis($recv_text); |
|
| 184 | 45 | $data = self::format_object_from_raw_strings($strings, $num); |
|
| 185 | 45 | unset($strings, $num, $recv_text); |
|
| 186 | 45 | $data->time_create = isset($data->time_create) ? (double) $data->time_create : null; |
|
| 187 | 45 | $data->time_expires = $this->get_expires_time($key); |
|
| 188 | 45 | $data->init_line = isset($data->init_line) ? (int) $data->init_line : null; |
|
| 189 | // Get |
||
| 190 | 45 | $this->redis_send(sprintf('get %s:value', $this->get_full_key_name($key))); |
|
| 191 | // @todo -LOADING Redis is loading the dataset in memory |
||
| 192 | 45 | $recv_text = $this->redis_recv(1); |
|
| 193 | 45 | if (!preg_match('_^\\$[0-9]+\\r\\n_s', $recv_text, $a)) { |
|
| 194 | 5 | $data->value = null; |
|
| 195 | |||
| 196 | 5 | return $data; |
|
| 197 | } |
||
| 198 | 45 | unset($a); |
|
| 199 | 45 | list($len, $recv_text) = explode("\r\n", substr($recv_text, 1), 2); |
|
| 200 | 45 | $len = (int) $len; |
|
| 201 | 45 | while (strlen($recv_text) < $len + 2) { |
|
| 202 | 5 | $recv_text .= $this->redis_recv(2); |
|
| 203 | 1 | } |
|
| 204 | |||
| 205 | 45 | $def = @gzinflate(base64_decode(substr($recv_text, 0, strlen($recv_text) - 2))); |
|
| 206 | 45 | if ($def === false) { |
|
| 207 | 5 | return null; |
|
| 208 | } |
||
| 209 | 45 | $data->value = unserialize($def); |
|
| 210 | |||
| 211 | 45 | return $data; |
|
| 212 | } |
||
| 213 | |||
| 214 | /** |
||
| 215 | * Получаем всё из hgetall |
||
| 216 | * |
||
| 217 | * Метод считает, что запрос уже сделан |
||
| 218 | * |
||
| 219 | * @param string $recv_text Заранее готовый текст, который уже был получен из redis'а |
||
| 220 | * |
||
| 221 | * @return string[][]|null[][]|integer[] |
||
| 222 | * @throws KeyValueException |
||
| 223 | */ |
||
| 224 | 45 | protected function get_hget_from_redis($recv_text) { |
|
| 225 | 45 | list($num, $recv_text) = explode("\r\n", substr($recv_text, 1), 2); |
|
| 226 | 45 | $num = (int) $num; |
|
| 227 | |||
| 228 | 45 | $strings = []; |
|
| 229 | 45 | for ($value_i = 0; $value_i < $num; $value_i++) { |
|
| 230 | 45 | while (!preg_match('_^(.*?)\\r\\n_', $recv_text, $a)) { |
|
| 231 | 5 | $recv_text .= $this->redis_recv(2); |
|
| 232 | 1 | } |
|
| 233 | 45 | if (!preg_match('_^\\$([0-9]+)\\r\\n(.*)$_s', $recv_text, $a)) { |
|
| 234 | 5 | throw new KeyValueException(self::format_error('Redis sent malformed response', $recv_text), |
|
| 235 | 5 | self::ERROR_CODE + 12); |
|
| 236 | } |
||
| 237 | |||
| 238 | 45 | $len = (int) $a[1]; |
|
| 239 | 45 | $need_length = 3 + strlen($a[1]) + $len + 2; |
|
| 240 | 45 | while ($need_length > strlen($recv_text)) { |
|
| 241 | 5 | $recv_text .= $this->redis_recv(2); |
|
| 242 | 1 | } |
|
| 243 | 45 | $strings[] = substr($recv_text, 3 + strlen($a[1]), $len); |
|
| 244 | 45 | $recv_text = substr($recv_text, $need_length); |
|
| 245 | 9 | } |
|
| 246 | |||
| 247 | 45 | return [$num, $strings]; |
|
| 248 | } |
||
| 249 | |||
| 250 | /** |
||
| 251 | * Пишем новое значение для key в нашем kv-хранилище |
||
| 252 | * |
||
| 253 | * @param string $key Название ключа |
||
| 254 | * @param mixed $value Новое значение |
||
| 255 | * @param double|integer $ttl Кол-во секунд, после которых значение будет считаться просроченным |
||
| 256 | * |
||
| 257 | * @throws KeyValueException |
||
| 258 | */ |
||
| 259 | 65 | function set_value($key, $value, $ttl = 315576000) { |
|
| 260 | 65 | $ts1 = microtime(true); |
|
| 261 | try { |
||
| 262 | 65 | $this->get_lock($key); |
|
| 263 | 65 | $this->set_value_low_level($key, $value, $ttl); |
|
| 264 | 35 | $this->release_lock(); |
|
| 265 | 35 | self::add_profiling(microtime(true) - $ts1, static::class, 'set_value'); |
|
| 266 | 37 | } catch (KeyValueException $e) { |
|
| 267 | 30 | $this->release_lock(); |
|
| 268 | 30 | self::add_profiling(microtime(true) - $ts1, static::class, 'set_value'); |
|
| 269 | 30 | throw $e; |
|
| 270 | } |
||
| 271 | 35 | } |
|
| 272 | |||
| 273 | /** |
||
| 274 | * Функция, непосредственно записывающая новое значение. Она не освобождает мьютексы |
||
| 275 | * |
||
| 276 | * @param string $key Название ключа |
||
| 277 | * @param mixed $value Новое значение |
||
| 278 | * @param double|integer $ttl Кол-во секунд, после которых значение будет считаться просроченным |
||
| 279 | * |
||
| 280 | * @throws KeyValueException |
||
| 281 | */ |
||
| 282 | 65 | protected function set_value_low_level($key, $value, $ttl) { |
|
| 283 | 65 | $values = $this->form_datum_value($key, null, $ttl); |
|
| 284 | 65 | $time_expires = $values->time_expires; |
|
| 285 | 65 | unset($values->value, $values->time_expires); |
|
| 286 | |||
| 287 | 65 | $texts = self::format_hmset_arguments($values); |
|
| 288 | 65 | unset($values); |
|
| 289 | 65 | $this->get_lock($key); |
|
| 290 | 65 | $this->redis_send('multi'); |
|
| 291 | 65 | $redis_answer = $this->redis_recv(1); |
|
| 292 | 65 | if ($redis_answer != "+OK\r\n") { |
|
| 293 | 5 | throw new KeyValueException(self::format_error('Can not start transaction', $redis_answer), self::ERROR_CODE + 5); |
|
| 294 | } |
||
| 295 | 60 | $full_key = $this->get_full_key_name($key); |
|
| 296 | // HMSet |
||
| 297 | 60 | $text = sprintf('hmset %s %s', $full_key, implode(' ', $texts)); |
|
| 298 | 60 | $this->redis_send($text); |
|
| 299 | 60 | $redis_answer = $this->redis_recv(1); |
|
| 300 | 60 | if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) { |
|
| 301 | 5 | throw new KeyValueException(self::format_error('Can not set meta info', $redis_answer), self::ERROR_CODE + 6); |
|
| 302 | } |
||
| 303 | 55 | unset($texts, $text); |
|
| 304 | // Ставим expires |
||
| 305 | 55 | $this->redis_send(sprintf('expireat %s %d', $full_key, ceil($time_expires))); |
|
| 306 | 55 | $redis_answer = $this->redis_recv(1); |
|
| 307 | 55 | if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) { |
|
| 308 | 5 | throw new KeyValueException(self::format_error('Can not set expires time', $redis_answer), self::ERROR_CODE + 7); |
|
| 309 | } |
||
| 310 | 50 | $full_value_string = base64_encode(gzdeflate(serialize($value))); |
|
| 311 | 50 | $u = false; |
|
| 312 | 50 | while (strlen($full_value_string) > 0) { |
|
| 313 | 50 | $this->redis_send(sprintf('%s %s:value "%s"', |
|
| 314 | 50 | $u ? 'append' : 'set', |
|
| 315 | 40 | $full_key, |
|
| 316 | 50 | substr($full_value_string, 0, $this->settings->string_chunk_size))); |
|
| 317 | 50 | $redis_answer = $this->redis_recv(1); |
|
| 318 | 50 | if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) { |
|
| 319 | 5 | throw new KeyValueException(self::format_error('Can not set value', $redis_answer), self::ERROR_CODE + 17); |
|
| 320 | } |
||
| 321 | 45 | $u = true; |
|
| 322 | |||
| 323 | 45 | $full_value_string = substr($full_value_string, $this->settings->string_chunk_size); |
|
| 324 | 9 | } |
|
| 325 | 45 | $this->redis_send(sprintf('expireat %s:value %d', $full_key, ceil($time_expires))); |
|
| 326 | 45 | $redis_answer = $this->redis_recv(1); |
|
| 327 | 45 | if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) { |
|
| 328 | 5 | throw new KeyValueException(self::format_error('Can not set expires time', $redis_answer), self::ERROR_CODE + 18); |
|
| 329 | } |
||
| 330 | |||
| 331 | // Коммитим |
||
| 332 | 40 | $this->redis_send('exec'); |
|
| 333 | 40 | $commit_text = $this->redis_recv(1); |
|
| 334 | 40 | if (!preg_match('_^\\*[0-9]+\\r\\n\\+OK\\r\\n_i', $commit_text)) { |
|
| 335 | 5 | $this->redis_send('discard'); |
|
| 336 | 5 | throw new KeyValueException(self::format_error('Can not commit transaction', $commit_text), self::ERROR_CODE + 8); |
|
| 337 | } |
||
| 338 | 35 | } |
|
| 339 | |||
| 340 | /** |
||
| 341 | * @param object $values |
||
| 342 | * |
||
| 343 | * @return string[] |
||
| 344 | */ |
||
| 345 | 65 | static function format_hmset_arguments($values) { |
|
| 346 | 65 | $texts = []; |
|
| 347 | 65 | foreach ($values as $v_k => &$v_v) { |
|
| 348 | 65 | if (is_null($v_v) or ($v_v === '')) { |
|
| 349 | 65 | $texts[] = sprintf('%s " "', $v_k);// @todo Это DAT КОСТЫЛЬ, потому что не знаю как передать empty string |
|
| 350 | 13 | } else { |
|
| 351 | 65 | $texts[] = sprintf('%s "%s"', $v_k, self::str_sanify($v_v)); |
|
| 352 | } |
||
| 353 | 13 | } |
|
| 354 | |||
| 355 | 65 | return $texts; |
|
| 356 | } |
||
| 357 | |||
| 358 | /** |
||
| 359 | * Форматируем объект из набора сырых строк, переданных из Redis'а |
||
| 360 | * |
||
| 361 | * @param string[] $strings |
||
| 362 | * @param integer $num |
||
| 363 | * |
||
| 364 | * @return object |
||
| 365 | */ |
||
| 366 | 45 | protected static function format_object_from_raw_strings($strings, $num) { |
|
| 367 | 45 | $data = new \stdClass(); |
|
| 368 | 45 | for ($value_i = 0; $value_i < $num; $value_i += 2) { |
|
| 369 | 45 | $value = $strings[$value_i + 1]; |
|
| 370 | 45 | if ($value == ' ') {// @todo DAT КОСТЫЛЬ, потому что не знаю как передать empty string |
|
| 371 | 35 | $value = ''; |
|
| 372 | 7 | } |
|
| 373 | 45 | $data->{$strings[$value_i]} = $value; |
|
| 374 | 9 | } |
|
| 375 | |||
| 376 | 45 | return $data; |
|
| 377 | } |
||
| 378 | |||
| 379 | /** |
||
| 380 | * @param string $key |
||
| 381 | * |
||
| 382 | * @throws KeyValueException |
||
| 383 | */ |
||
| 384 | 40 | function delete_value($key) { |
|
| 385 | 40 | $ts1 = microtime(true); |
|
| 386 | try { |
||
| 387 | 40 | $this->get_lock($key); |
|
| 388 | 40 | $this->delete_value_low_level($key); |
|
| 389 | 40 | $this->release_lock(); |
|
| 390 | 40 | self::add_profiling(microtime(true) - $ts1, static::class, 'delete_value'); |
|
| 391 | 12 | } catch (KeyValueException $e) { |
|
| 392 | 5 | $this->release_lock(); |
|
| 393 | 5 | self::add_profiling(microtime(true) - $ts1, static::class, 'delete_value'); |
|
| 394 | 5 | throw $e; |
|
| 395 | } |
||
| 396 | 40 | } |
|
| 397 | |||
| 398 | /** |
||
| 399 | * @param string $key |
||
| 400 | * |
||
| 401 | * @throws KeyValueException |
||
| 402 | */ |
||
| 403 | 40 | protected function delete_value_low_level($key) { |
|
| 404 | 40 | foreach ([$this->get_full_key_name($key), $this->get_full_key_name($key).':value'] as $full_key) { |
|
| 405 | 40 | $this->redis_send(sprintf('del %s', $full_key)); |
|
| 406 | 40 | $commit_text = $this->redis_recv(1); |
|
| 407 | 40 | if (!preg_match('_^\\:[0-9]+\\r\\n_', $commit_text)) { |
|
| 408 | 5 | throw new KeyValueException(self::format_error('Can not delete key', $commit_text), |
|
| 409 | 19 | self::ERROR_CODE + 9); |
|
| 410 | } |
||
| 411 | 8 | } |
|
| 412 | 40 | } |
|
| 413 | |||
| 414 | // @todo Имплементировать get_change_time |
||
| 415 | |||
| 416 | /** |
||
| 417 | * Время просрочки значения, если есть |
||
| 418 | * |
||
| 419 | * @param string $key |
||
| 420 | * |
||
| 421 | * @return double|null |
||
| 422 | * @throws KeyValueException |
||
| 423 | */ |
||
| 424 | 55 | function get_expires_time($key) { |
|
| 425 | 55 | $this->redis_send(sprintf('ttl %s', $this->get_full_key_name($key))); |
|
| 426 | 55 | $commit_text = $this->redis_recv(1); |
|
| 427 | 55 | if (!preg_match('_^\\:(\\-?[0-9]+)_', $commit_text, $a)) { |
|
| 428 | 5 | return null; |
|
| 429 | } |
||
| 430 | 55 | if ($a[1] < 0) { |
|
| 431 | 5 | return null; |
|
| 432 | } else { |
||
| 433 | 50 | return time() + $a[1]; |
|
| 434 | } |
||
| 435 | } |
||
| 436 | |||
| 437 | /** |
||
| 438 | * Обновляем срок жизни записи |
||
| 439 | * |
||
| 440 | * @param string $key Название ключа |
||
| 441 | * @param double $ttl Кол-во секунд, после которых значение будет считаться просроченным |
||
| 442 | * |
||
| 443 | * @throws KeyValueException |
||
| 444 | */ |
||
| 445 | 15 | function set_expires_time($key, $ttl) { |
|
| 446 | 15 | $current_ttl = $this->get_expires_time($key); |
|
| 447 | 15 | if (is_null($current_ttl)) { |
|
| 448 | // Нет такого объекта |
||
| 449 | return; |
||
| 450 | } |
||
| 451 | |||
| 452 | 15 | $this->redis_send('multi'); |
|
| 453 | 15 | $redis_answer = $this->redis_recv(1); |
|
| 454 | 15 | if ($redis_answer != "+OK\r\n") { |
|
| 455 | 5 | throw new KeyValueException(self::format_error('Can not start transaction', $redis_answer), |
|
| 456 | 5 | self::ERROR_CODE + 13); |
|
| 457 | } |
||
| 458 | 15 | $full_key = $this->get_full_key_name($key); |
|
| 459 | // Ставим expires |
||
| 460 | 15 | $this->redis_send(sprintf('expireat %s %d', $full_key, ceil(time() + $ttl))); |
|
| 461 | 15 | $redis_answer = $this->redis_recv(1); |
|
| 462 | 15 | if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) { |
|
| 463 | 5 | throw new KeyValueException(self::format_error('Can not set expires time', $redis_answer), self::ERROR_CODE + 14); |
|
| 464 | } |
||
| 465 | 15 | $this->redis_send(sprintf('expireat %s:value %d', $full_key, ceil(time() + $ttl))); |
|
| 466 | 15 | $redis_answer = $this->redis_recv(1); |
|
| 467 | 15 | if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) { |
|
| 468 | 5 | throw new KeyValueException(self::format_error('Can not set expires time', $redis_answer), self::ERROR_CODE + 15); |
|
| 469 | } |
||
| 470 | |||
| 471 | // Коммитим |
||
| 472 | 15 | $this->redis_send('exec'); |
|
| 473 | 15 | $need_string = "*2\r\n:1\r\n:1\r\n"; |
|
| 474 | 15 | $commit_text = ''; |
|
| 475 | do { |
||
| 476 | 15 | $commit_text .= $this->redis_recv(1); |
|
| 477 | 15 | } while (strlen($commit_text) < strlen($need_string)); |
|
| 478 | 15 | if ($commit_text != $need_string) { |
|
| 479 | 5 | $this->redis_send('discard'); |
|
| 480 | 5 | throw new KeyValueException(self::format_error('Can not commit transaction', $commit_text), |
|
| 481 | 5 | self::ERROR_CODE + 17); |
|
| 482 | } |
||
| 483 | 15 | } |
|
| 484 | |||
| 485 | /** |
||
| 486 | * @param string $error |
||
| 487 | * @param string $text |
||
| 488 | * |
||
| 489 | * @return string string |
||
| 490 | */ |
||
| 491 | 50 | static function format_error($error, $text) { |
|
| 492 | 50 | if (preg_match('_^\\-ERR\\s+(.+?)\\s*$_mi', $text, $a)) { |
|
| 493 | 45 | return sprintf('%s: %s', $error, $a[1]); |
|
| 494 | 10 | } elseif (preg_match('_^\\-\\s*(.+?)\\s*$_mi', $text, $a)) { |
|
| 495 | 5 | return sprintf('%s: %s', $error, $a[1]); |
|
| 496 | } else { |
||
| 497 | 10 | return $error; |
|
| 498 | } |
||
| 499 | } |
||
| 500 | |||
| 501 | 65 | static function str_sanify($text) { |
|
| 502 | 65 | return str_replace([ |
|
| 503 | 65 | '\\', |
|
| 504 | 13 | "'", |
|
| 505 | 13 | '"', |
|
| 506 | 13 | "\r", |
|
| 507 | 13 | "\n", |
|
| 508 | 13 | ], [ |
|
| 509 | 65 | '\\\\', |
|
| 510 | 13 | "\\'", |
|
| 511 | 13 | '\\"', |
|
| 512 | 13 | "\\r", |
|
| 513 | 13 | "\\n", |
|
| 514 | 52 | ], $text); |
|
| 515 | } |
||
| 516 | |||
| 517 | /** |
||
| 518 | * Создаём мьютекс, соответствующий ключу и кладём его в _locks |
||
| 519 | * |
||
| 520 | * @param string $key |
||
| 521 | */ |
||
| 522 | 85 | protected function create_lock($key) { |
|
| 523 | 85 | if ($this->_mutex_root_folder == null) { |
|
| 524 | 85 | $folder = null; |
|
| 525 | 21 | } elseif ($this->settings->multi_folder_mutex) { |
|
| 526 | 5 | $hash = hash('sha512', $key); |
|
| 527 | 5 | $folder = $this->_mutex_root_folder.sprintf('/%s/%s', substr($hash, 0, 2), substr($hash, 2, 2)); |
|
| 528 | 5 | if (!file_exists($this->_mutex_root_folder.'/'.substr($hash, 0, 2))) { |
|
| 529 | 5 | mkdir($this->_mutex_root_folder.'/'.substr($hash, 0, 2)); |
|
| 530 | 1 | } |
|
| 531 | 5 | if (!file_exists($folder)) { |
|
| 532 | 5 | mkdir($folder); |
|
| 533 | 1 | } |
|
| 534 | 1 | } else { |
|
| 535 | 5 | $folder = $this->_mutex_root_folder; |
|
| 536 | } |
||
| 537 | |||
| 538 | // @todo Любой мьютекс |
||
| 539 | 85 | $this->_locks[$key] = new FileMutex([ |
|
| 540 | 85 | 'name' => 'ascetkey_'.$key, |
|
| 541 | 85 | 'prefix' => '', |
|
| 542 | 85 | 'folder' => $folder, |
|
| 543 | 17 | ]); |
|
| 544 | 85 | } |
|
| 545 | |||
| 546 | /** |
||
| 547 | * @throws KeyValueException |
||
| 548 | */ |
||
| 549 | 5 | function clear() { |
|
| 550 | 5 | $this->redis_send(sprintf('keys %s', $this->get_full_key_name('*'))); |
|
| 551 | 5 | $commit_text = $this->redis_recv(2); |
|
| 552 | 5 | if (!preg_match('_^\\*([0-9]+)\\r\\n_', $commit_text, $a)) { |
|
| 553 | throw new KeyValueException(self::format_error('Can not delete keys', $commit_text), |
||
| 554 | self::ERROR_CODE + 20); |
||
| 555 | } |
||
| 556 | 5 | $count = (int) $a[1]; |
|
| 557 | |||
| 558 | 5 | $strings = explode("\r\n", $commit_text); |
|
| 559 | 5 | $list = []; |
|
| 560 | 5 | for ($offset = 2; $offset <= 1 + 2 * $count; $offset += 2) { |
|
| 561 | 5 | $list[] = $strings[$offset]; |
|
| 562 | 1 | } |
|
| 563 | 5 | unset($offset, $count, $strings, $commit_text, $a); |
|
| 564 | |||
| 565 | 5 | foreach (array_chunk($list, 20) as &$sub_list) { |
|
|
0 ignored issues
–
show
The expression
array_chunk($list, 20) cannot be used as a reference.
Let?s assume that you have the following foreach ($array as &$itemValue) { }
However, if we were to replace foreach (getArray() as &$itemValue) { }
then assigning by reference is not possible anymore as there is no target that could be modified. Available Fixes1. Do not assign by referenceforeach (getArray() as $itemValue) { }
2. Assign to a local variable first$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a referencefunction &getArray() { $array = array(); return $array; }
foreach (getArray() as &$itemValue) { }
Loading history...
|
|||
| 566 | 5 | $this->redis_send(sprintf('del %s', implode(' ', $sub_list))); |
|
| 567 | 5 | $commit_text = $this->redis_recv(1); |
|
| 568 | 5 | if (!preg_match('_^\\:([0-9]+)_', $commit_text, $a)) { |
|
| 569 | 2 | throw new KeyValueException('Can not delete keys while doing "clear"'); |
|
| 570 | } |
||
| 571 | 1 | } |
|
| 572 | 5 | } |
|
| 573 | } |
||
| 574 | |||
| 575 | ?> |
If you suppress an error, we recommend checking for the error condition explicitly: