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) { }
![]() |
|||
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: