Test Failed
Push — master ( c137c2...5643bc )
by Никита
18:22
created

RedisStorage::delete_value_low_level()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 8
cts 8
cp 1
rs 9.6666
c 0
b 0
f 0
nc 3
cc 3
eloc 6
nop 1
crap 3
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 312
        function __construct($settings) {
1 ignored issue
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
26
            $default_settings = [
27 312
                'database' => 0,
28 312
                'storage_type' => self::StoragePersistent,
29 312
                'host' => '127.0.0.1',
30 312
                'port' => 6379,
31 312
                'timeout' => self::REDIS_TIMEOUT_LIMIT,
32 312
                'string_chunk_size' => self::REDIS_STRING_CHUNK_SIZE,
33 104
            ];
34 312
            foreach ($default_settings as $key => &$default_value) {
35 312
                if (!isset($settings->{$key})) {
36 312
                    $settings->{$key} = $default_value;
37 104
                }
38 104
            }
39
40 312
            $this->settings = $settings;
41 312
            $this->standard_prefix_strategy();
42 312
            if (isset($settings->folder)) {
43 6
                $this->_mutex_root_folder = $settings->folder;
44 2
            }
45 312
        }
46
47 312
        function __destruct() {
1 ignored issue
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
48 312
            if (!is_null($this->_socket)) {
49 120
                if (@socket_shutdown($this->_socket)) {
50 120
                    @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 40
                }
52 40
            }
53 312
        }
54
55
        /**
56
         * @throws KeyValueException
57
         */
58 126
        protected function lazy_init() {
59 126
            if (!is_null($this->_socket)) {
60 114
                return;
61
            }
62 126
            $this->_socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
63 126
            if ($this->_socket === false) {
64
                // @codeCoverageIgnoreStart
65
                throw new KeyValueException('Can not create socket', self::ERROR_CODE + 0);
66
                // @codeCoverageIgnoreEnd
67
            }
68 126
            $u = @socket_connect($this->_socket, $this->settings->host, $this->settings->port);
69 126
            if (!$u) {
70 6
                throw new KeyValueException('Can not connect to '.$this->settings->host.':'.$this->settings->port,
71 6
                    self::ERROR_CODE + 1);
72
            }
73 126
            if ($this->settings->database != 0) {
74 6
                $this->redis_send(sprintf('select %d', $this->settings->database));
75 6
                $redis_answer = $this->redis_recv(1);
76 6
                if ($redis_answer != "+OK\r\n") {
77 6
                    throw new KeyValueException(self::format_error('Can not select database '.$this->settings->database,
78 6
                        $redis_answer), self::ERROR_CODE + 3);
79
                }
80 2
            }
81 126
        }
82
83
        /**
84
         * @param string $command
85
         *
86
         * @throws KeyValueException
87
         */
88 126
        function redis_send($command) {
1 ignored issue
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
89 126
            $this->lazy_init();
90 126
            $command .= "\n";
91 126
            $i = @socket_write($this->_socket, $command, strlen($command));
92 126
            if (($i === false) or ($i != strlen($command))) {
93 6
                throw new KeyValueException('Can not send command', self::ERROR_CODE + 2);
94
            }
95 120
        }
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 120
        protected function redis_recv($mode = 1) {
109 120
            $buf = '';
110 120
            $r = [$this->_socket];
111 120
            $o = null;
112
            do {
113 120
                $count = @socket_select($r, $o, $o, (($mode !== 2) or ($buf == '')) ? $this->settings->timeout : 0);
114 120
                if ($count === false) {
115 6
                    throw new KeyValueException('Redis server read error', self::ERROR_CODE + 16);
116 120
                } elseif (($count === 0) and ($mode !== 2)) {
117 6
                    throw new KeyValueException('Redis server timeout', self::ERROR_CODE + 10);
118 120
                } elseif ($count === 0) {
119 12
                    return $buf;
120
                }
121 120
                $i = @socket_recv($this->_socket, $buf_chunk, self::REDIS_RECV_CHUNK_SIZE, 0);
122 120
                if ($i === false) {
123
                    // @codeCoverageIgnoreStart
124
                    throw new KeyValueException('Can not read from redis server', self::ERROR_CODE + 4);
125
                    // @codeCoverageIgnoreEnd
126
                }
127 120
                $buf .= $buf_chunk;
128 120
                if (($mode === 1) and (strpos($buf, "\r\n") !== false)) {
129 120
                    return $buf;
130
                }
131 18
            } while ((($mode === 0) and (strlen($buf) < self::REDIS_RECV_CHUNK_SIZE)) or in_array($mode, [1, 2]));
132
133 6
            return $buf;
134
        }
135
136
        /**
137
         * @param string $key
138
         *
139
         * @return string
140
         */
141 108
        protected function get_full_key_name($key) {
142 108
            return sprintf('%s:%s:%s', self::KeyPrefix, $this->_prefix, $key);
143
        }
144
145
        /**
146
         * @param string $key
147
         *
148
         * @return object|null
149
         */
150 54
        protected function get_value_full_clear($key) {
151
            try {
152 54
                $this->get_lock($key);
153 54
                $value = $this->get_value_full_clear_low_level($key);
154 48
                $this->release_lock();
155
156 48
                return $value;
157 6
            } catch (KeyValueException $e) {
158 6
                $this->release_lock();
159
160 6
                return null;
161
            }
162
        }
163
164
        /**
165
         * @param string $key
166
         *
167
         * @return object|null
168
         * @throws KeyValueException
169
         */
170 54
        protected function get_value_full_clear_low_level($key) {
171 54
            $this->redis_send(sprintf('hgetall %s', $this->get_full_key_name($key)));
172
            // @todo -LOADING Redis is loading the dataset in memory
173 54
            $recv_text = $this->redis_recv(1);
174 54
            if ($recv_text == "*0\r\n") {
175 42
                return null;
176
            }
177 48
            if (substr($recv_text, 0, 1) != '*') {
178 6
                throw new KeyValueException(self::format_error('Redis sent malformed response', $recv_text),
179 6
                    self::ERROR_CODE + 11);
180
            }
181 48
            list($num, $strings) = $this->get_hget_from_redis($recv_text);
182 48
            $data = self::format_object_from_raw_strings($strings, $num);
183 48
            unset($strings, $num, $recv_text);
184 48
            $data->time_create = isset($data->time_create) ? (double) $data->time_create : null;
185 48
            $data->time_expires = $this->get_expires_time($key);
186 48
            $data->init_line = isset($data->init_line) ? (int) $data->init_line : null;
187
            // Get
188 48
            $this->redis_send(sprintf('get %s:value', $this->get_full_key_name($key)));
189
            // @todo -LOADING Redis is loading the dataset in memory
190 48
            $recv_text = $this->redis_recv(1);
191 48
            if (!preg_match('_^\\$[0-9]+\\r\\n_s', $recv_text, $a)) {
192 6
                $data->value = null;
193
194 6
                return $data;
195
            }
196 48
            unset($a);
197 48
            list($len, $recv_text) = explode("\r\n", substr($recv_text, 1), 2);
198 48
            $len = (int) $len;
199 48
            while (strlen($recv_text) < $len + 2) {
200 6
                $recv_text .= $this->redis_recv(2);
201 2
            }
202
203 48
            $def = @gzinflate(base64_decode(substr($recv_text, 0, strlen($recv_text) - 2)));
204 48
            if ($def === false) {
205 6
                return null;
206
            }
207 48
            $data->value = unserialize($def);
208
209 48
            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 48
        protected function get_hget_from_redis($recv_text) {
223 48
            list($num, $recv_text) = explode("\r\n", substr($recv_text, 1), 2);
224 48
            $num = (int) $num;
225
226 48
            $strings = [];
227 48
            for ($value_i = 0; $value_i < $num; $value_i++) {
228 48
                while (!preg_match('_^(.*?)\\r\\n_', $recv_text, $a)) {
229 6
                    $recv_text .= $this->redis_recv(2);
230 2
                }
231 48
                if (!preg_match('_^\\$([0-9]+)\\r\\n(.*)$_s', $recv_text, $a)) {
232 6
                    throw new KeyValueException(self::format_error('Redis sent malformed response', $recv_text),
233 6
                        self::ERROR_CODE + 12);
234
                }
235
236 48
                $len = (int) $a[1];
237 48
                $need_length = 3 + strlen($a[1]) + $len + 2;
238 48
                while ($need_length > strlen($recv_text)) {
239 6
                    $recv_text .= $this->redis_recv(2);
240 2
                }
241 48
                $strings[] = substr($recv_text, 3 + strlen($a[1]), $len);
242 48
                $recv_text = substr($recv_text, $need_length);
243 16
            }
244
245 48
            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 72
        function set_value($key, $value, $ttl = 315576000) {
1 ignored issue
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
258 72
            $ts1 = microtime(true);
259
            try {
260 72
                $this->get_lock($key);
261 72
                $this->set_value_low_level($key, $value, $ttl);
262 36
                $this->release_lock();
263 36
                self::add_profiling(microtime(true) - $ts1, static::class, 'set_value');
264 48
            } catch (KeyValueException $e) {
265 36
                $this->release_lock();
266 36
                self::add_profiling(microtime(true) - $ts1, static::class, 'set_value');
267 36
                throw $e;
268
            }
269 36
        }
270
271
        /**
272
         * Функция, непосредственно записывающая новое значение. Она не освобождает мьютексы
273
         *
274
         * @param string         $key   Название ключа
275
         * @param mixed          $value Новое значение
276
         * @param double|integer $ttl   Кол-во секунд, после которых значение будет считаться просроченным
277
         *
278
         * @throws KeyValueException
279
         */
280 72
        protected function set_value_low_level($key, $value, $ttl) {
281 72
            $values = $this->form_datum_value($key, null, $ttl);
282 72
            $time_expires = $values->time_expires;
283 72
            unset($values->value, $values->time_expires);
284
285 72
            $texts = self::format_hmset_arguments($values);
286 72
            unset($values);
287 72
            $this->redis_send('multi');
288 72
            $redis_answer = $this->redis_recv(1);
289 72
            if ($redis_answer != "+OK\r\n") {
290 6
                throw new KeyValueException(self::format_error('Can not start transaction', $redis_answer), self::ERROR_CODE + 5);
291
            }
292 66
            $full_key = $this->get_full_key_name($key);
293
            // HMSet
294 66
            $text = sprintf('hmset %s %s', $full_key, implode(' ', $texts));
295 66
            $this->redis_send($text);
296 66
            $redis_answer = $this->redis_recv(1);
297 66
            if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) {
298 6
                throw new KeyValueException(self::format_error('Can not set meta info', $redis_answer), self::ERROR_CODE + 6);
299
            }
300 60
            unset($texts, $text);
301
            // Ставим expires
302 60
            $this->redis_send(sprintf('expireat %s %d', $full_key, ceil($time_expires)));
303 60
            $redis_answer = $this->redis_recv(1);
304 60
            if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) {
305 6
                throw new KeyValueException(self::format_error('Can not set expires time', $redis_answer), self::ERROR_CODE + 7);
306
            }
307 54
            $full_value_string = base64_encode(gzdeflate(serialize($value)));
308 54
            $u = false;
309 54
            while (strlen($full_value_string) > 0) {
310 54
                $this->redis_send(sprintf('%s %s:value "%s"',
311 54
                    $u ? 'append' : 'set',
312 18
                    $full_key,
313 54
                    substr($full_value_string, 0, $this->settings->string_chunk_size)));
314 54
                $redis_answer = $this->redis_recv(1);
315 54
                if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) {
316 6
                    throw new KeyValueException(self::format_error('Can not set value', $redis_answer), self::ERROR_CODE + 17);
317
                }
318 48
                $u = true;
319
320 48
                $full_value_string = substr($full_value_string, $this->settings->string_chunk_size);
321 16
            }
322 48
            $this->redis_send(sprintf('expireat %s:value %d', $full_key, ceil($time_expires)));
323 48
            $redis_answer = $this->redis_recv(1);
324 48
            if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) {
325 6
                throw new KeyValueException(self::format_error('Can not set expires time', $redis_answer), self::ERROR_CODE + 18);
326
            }
327
328
            // Коммитим
329 42
            $this->redis_send('exec');
330 42
            $commit_text = $this->redis_recv(1);
331 42
            if (!preg_match('_^\\*[0-9]+\\r\\n\\+OK\\r\\n_i', $commit_text)) {
332 6
                $this->redis_send('discard');
333 6
                throw new KeyValueException(self::format_error('Can not commit transaction', $commit_text), self::ERROR_CODE + 8);
334
            }
335 36
        }
336
337
        /**
338
         * @param object $values
339
         *
340
         * @return string[]
341
         */
342 72
        static function format_hmset_arguments($values) {
343 72
            $texts = [];
344 72
            foreach ($values as $v_k => &$v_v) {
345 72
                if (is_null($v_v) or ($v_v === '')) {
346 72
                    $texts[] = sprintf('%s " "', $v_k);// @todo Это DAT КОСТЫЛЬ, потому что не знаю как передать empty string
347 24
                } else {
348 72
                    $texts[] = sprintf('%s "%s"', $v_k, self::str_sanify($v_v));
349
                }
350 24
            }
351
352 72
            return $texts;
353
        }
354
355
        /**
356
         * Форматируем объект из набора сырых строк, переданных из Redis'а
357
         *
358
         * @param string[] $strings
359
         * @param integer  $num
360
         *
361
         * @return object
362
         */
363 48
        protected static function format_object_from_raw_strings($strings, $num) {
364 48
            $data = new \stdClass();
365 48
            for ($value_i = 0; $value_i < $num; $value_i += 2) {
366 48
                $value = $strings[$value_i + 1];
367 48
                if ($value == ' ') {// @todo DAT КОСТЫЛЬ, потому что не знаю как передать empty string
368 36
                    $value = '';
369 12
                }
370 48
                $data->{$strings[$value_i]} = $value;
371 16
            }
372
373 48
            return $data;
374
        }
375
376
        /**
377
         * @param string $key
378
         *
379
         * @throws KeyValueException
380
         */
381 42
        function delete_value($key) {
382 42
            $ts1 = microtime(true);
383
            try {
384 42
                $this->get_lock($key);
385 42
                $this->delete_value_low_level($key);
386 42
                $this->release_lock();
387 42
                self::add_profiling(microtime(true) - $ts1, static::class, 'delete_value');
388 18
            } catch (KeyValueException $e) {
389 6
                $this->release_lock();
390 6
                self::add_profiling(microtime(true) - $ts1, static::class, 'delete_value');
391 6
                throw $e;
392
            }
393 42
        }
394
395 42
        protected function delete_value_low_level($key) {
396 42
            foreach ([$this->get_full_key_name($key), $this->get_full_key_name($key).':value'] as $full_key) {
397 42
                $this->redis_send(sprintf('del %s', $full_key));
398 42
                $commit_text = $this->redis_recv(1);
399 42
                if (!preg_match('_^\\:[0-9]+\\r\\n_', $commit_text)) {
400 18
                    throw new KeyValueException(self::format_error('Can not delete key', $commit_text), self::ERROR_CODE + 9);
401
                }
402 14
            }
403 42
        }
404
405
        // @todo Имплементировать get_change_time
406
407
        /**
408
         * Время просрочки значения, если есть
409
         *
410
         * @param string $key
411
         *
412
         * @return double|null
413
         */
414 54
        function get_expires_time($key) {
415 54
            $this->redis_send(sprintf('ttl %s', $this->get_full_key_name($key)));
416 54
            $commit_text = $this->redis_recv(1);
417 54
            if (!preg_match('_^\\:(\\-?[0-9]+)_', $commit_text, $a)) {
418 6
                return null;
419
            }
420 54
            if ($a[1] < 0) {
421 6
                return null;
422
            } else {
423 48
                return time() + $a[1];
424
            }
425
        }
426
427
        /**
428
         * Обновляем срок жизни записи
429
         *
430
         * @param string $key Название ключа
431
         * @param double $ttl Кол-во секунд, после которых значение будет считаться просроченным
432
         *
433
         * @throws KeyValueException
434
         */
435 18
        function set_expires_time($key, $ttl) {
436 18
            $this->redis_send('multi');
437 18
            $redis_answer = $this->redis_recv(1);
438 18
            if ($redis_answer != "+OK\r\n") {
439 6
                throw new KeyValueException(self::format_error('Can not start transaction', $redis_answer),
440 6
                    self::ERROR_CODE + 13);
441
            }
442 18
            $full_key = $this->get_full_key_name($key);
443
            // Ставим expires
444 18
            $this->redis_send(sprintf('expireat %s %d', $full_key, ceil(time() + $ttl)));
445 18
            $redis_answer = $this->redis_recv(1);
446 18
            if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) {
447 6
                throw new KeyValueException(self::format_error('Can not set expires time', $redis_answer), self::ERROR_CODE + 14);
448
            }
449 18
            $this->redis_send(sprintf('expireat %s:value %d', $full_key, ceil(time() + $ttl)));
450 18
            $redis_answer = $this->redis_recv(1);
451 18
            if (!in_array($redis_answer, ["+OK\r\n", "+QUEUED\r\n"])) {
452 6
                throw new KeyValueException(self::format_error('Can not set expires time', $redis_answer), self::ERROR_CODE + 15);
453
            }
454
455
            // Коммитим
456 18
            $this->redis_send('exec');
457 18
            $need_string = "*2\r\n:1\r\n:1\r\n";
458 18
            $commit_text = '';
459
            do {
460 18
                $commit_text .= $this->redis_recv(1);
461 18
            } while (strlen($commit_text) < strlen($need_string));
462 18
            if ($commit_text != $need_string) {
463 6
                $this->redis_send('discard');
464 6
                throw new KeyValueException(self::format_error('Can not commit transaction', $commit_text),
465 6
                    self::ERROR_CODE + 17);
466
            }
467 18
        }
468
469
        /**
470
         * @param string $error
471
         * @param string $text
472
         *
473
         * @return string string
474
         */
475 60
        static function format_error($error, $text) {
476 60
            if (preg_match('_^\\-ERR\\s+(.+?)\\s*$_mi', $text, $a)) {
477 54
                return sprintf('%s: %s', $error, $a[1]);
478 12
            } elseif (preg_match('_^\\-\\s*(.+?)\\s*$_mi', $text, $a)) {
479 6
                return sprintf('%s: %s', $error, $a[1]);
480
            } else {
481 12
                return $error;
482
            }
483
        }
484
485 72
        static function str_sanify($text) {
486 72
            return str_replace([
487 72
                '\\',
488 24
                "'",
489 24
                '"',
490 24
                "\r",
491 24
                "\n",
492 24
            ], [
493 72
                '\\\\',
494 24
                "\\'",
495 24
                '\\"',
496 24
                "\\r",
497 24
                "\\n",
498 24
            ], $text);
499
        }
500
501
        /**
502
         * Создаём мьютекс, соответствующий ключу и кладём его в _locks
503
         *
504
         * @param string $key
505
         */
506 96
        protected function create_lock($key) {
507 96
            if ($this->_mutex_root_folder == null) {
508 96
                $folder = null;
509 36
            } elseif ($this->settings->multi_folder_mutex) {
510 6
                $hash = hash('sha512', $key);
511 6
                $folder = $this->_mutex_root_folder.sprintf('/%s/%s', substr($hash, 0, 2), substr($hash, 2, 2));
512 6
                if (!file_exists($this->_mutex_root_folder.'/'.substr($hash, 0, 2))) {
513 6
                    mkdir($this->_mutex_root_folder.'/'.substr($hash, 0, 2));
514 2
                }
515 6
                if (!file_exists($folder)) {
516 6
                    mkdir($folder);
517 2
                }
518 2
            } else {
519 6
                $folder = $this->_mutex_root_folder;
520
            }
521
522
            // @todo Любой мьютекс
523 96
            $this->_locks[$key] = new FileMutex([
524 96
                'name' => 'ascetkey_'.$key,
525 96
                'type' => MutexInterface::SERVER,
526 96
                'folder' => $folder,
527 32
            ]);
528 96
        }
529
530
        /**
531
         * @throws KeyValueException
532
         */
533 6
        function clear() {
534 6
            $this->redis_send(sprintf('keys %s', $this->get_full_key_name('*')));
535 6
            $commit_text = $this->redis_recv(2);
536 6
            if (!preg_match('_^\\*([0-9]+)\\r\\n_', $commit_text, $a)) {
537
                throw new KeyValueException(self::format_error('Can not delete keys', $commit_text),
538
                    self::ERROR_CODE + 20);
539
            }
540 6
            $count = (int) $a[1];
541
542 6
            $strings = explode("\r\n", $commit_text);
543 6
            $list = [];
544 6
            for ($offset = 2; $offset <= 1 + 2 * $count; $offset += 2) {
545 6
                $list[] = $strings[$offset];
546 2
            }
547 6
            unset($offset, $count, $strings, $commit_text, $a);
548
549 6
            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...
550 6
                $this->redis_send(sprintf('del %s', implode(' ', $sub_list)));
551 6
                $commit_text = $this->redis_recv(1);
552 6
                if (!preg_match('_^\\:([0-9]+)_', $commit_text, $a)) {
553 2
                    throw new KeyValueException('Can not delete keys while doing "clear"');
554
                }
555 2
            }
556 6
        }
557
    }
558
559
?>
1 ignored issue
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...