Completed
Branch master (bade77)
by Никита
05:11 queued 03:30
created

RedisStorage::format_hmset_arguments()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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