RedisStorage::get_value_full_clear_low_level()   B
last analyzed

Complexity

Conditions 8
Paths 22

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 41
ccs 30
cts 30
cp 1
rs 8.0195
c 0
b 0
f 0
cc 8
nc 22
nop 1
crap 8
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
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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
Bug introduced by
The expression array_chunk($list, 20) cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &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
?>