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

AbstractStorage::delete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
1
<?php
2
3
    namespace NokitaKaze\KeyValue;
4
5
    use NokitaKaze\Mutex\MutexInterface;
6
    use NokitaKaze\Mutex\FileMutex;
7
    use Psr\SimpleCache\CacheInterface;
8
9
    /**
10
     * Class AbstractStorage
11
     * @package NokitaKaze\KeyValue
12
     * @doc http://www.php-fig.org/psr/psr-16/
13
     */
14
    abstract class AbstractStorage implements CacheInterface {
15
        const StorageTemporary = 0;
16
        const StoragePersistent = 1;
17
        const RegionDomain = 0;// Только в контексте этого HostName
18
        const RegionServer = 1;// В контексте ВСЕГО дедика
19
        const RegionFolder = 2;// В контексте папки домена (для разных хостов, запущенных из одной папки)
20
21
        /**
22
         * @var string Префикс к названиям полей ключей для бесконфликтного использования в глобалсе
23
         */
24
        protected $_prefix;
25
26
        /**
27
         * @var KeyValueSettings|object Настройки, переданные в конструктор и конструктором исправленные
28
         */
29
        protected $settings;
30
31
        /**
32
         * @var MutexInterface[]|null[]
33
         * Мьютекс, созданный через MutexInterface, настройки задаются конкретной реализацией
34
         */
35
        protected $_locks = [];
36
37
        /**
38
         * @var string|null
39
         */
40
        protected $_last_used_lock_key = null;
41
42
        /**
43
         * @param KeyValueSettings|object $settings
44
         */
45
        abstract 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...
46
47
        /**
48
         * Дёргаем значение из kv-хранилища
49
         *
50
         * @param string       $key           Название ключа
51
         * @param string|mixed $default_value Дефолтное значение, если настоящее недоступно
52
         *
53
         * @return string|mixed
54
         */
55 78
        function get_value($key, $default_value = '') {
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...
56 78
            $data = $this->get_value_full($key);
57
58 78
            return is_null($data) ? $default_value : $data->value;
59
        }
60
61
        /**
62
         * Формируем datum типа объект, который мы будем вносить в наше kv-хранилище
63
         *
64
         * @param string|integer $key   Ключ
65
         * @param mixed          $value Новое значение
66
         * @param double         $ttl   Кол-во секунд, после которых значение будет считаться просроченным
67
         *
68
         * @return object
69
         */
70 156
        protected function form_datum_value($key, $value, $ttl) {
71 156
            $backtrace = debug_backtrace();
72
            $data = (object) [
73 156
                'key' => $key,
74 156
                'time_create' => microtime(true),
75 156
                'time_expires' => microtime(true) + $ttl,
76 156
                'host' => isset($_SERVER, $_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null,
77 156
                'value' => $value,
78 156
                'init_file' => isset($backtrace[1], $backtrace[1]['file']) ? $backtrace[1]['file'] : null,
79 156
                'init_line' => isset($backtrace[1], $backtrace[1]['line']) ? $backtrace[1]['line'] : null,
80 52
            ];
81 156
            unset($backtrace);
82
83 156
            return $data;
84
        }
85
86
        /**
87
         * Пишем новое значение для key в нашем kv-хранилище
88
         *
89
         * @param string         $key   Название ключа
90
         * @param mixed          $value Новое значение
91
         * @param double|integer $ttl   Кол-во секунд, после которых значение будет считаться просроченным
92
         *
93
         * @throws KeyValueException
94
         */
95
        abstract 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...
96
97
        /**
98
         * @param string $key
99
         *
100
         * @return object|null
101
         */
102
        abstract protected function get_value_full_clear($key);
103
104
        /**
105
         * Получение полных данных по вхождению в kv-хранилище
106
         *
107
         * @param string $key Название ключа
108
         *
109
         * @return object|KeyValueDatum|null
110
         */
111 144
        function get_value_full($key) {
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...
112 144
            $ts1 = microtime(true);
113 144
            $data = $this->get_value_full_clear($key);
114 144
            self::add_profiling(microtime(true) - $ts1, static::class, 'get_value_full_clear');
115 144
            if (is_null($data)) {
116 138
                return null;
117
            }
118
119
            // Проверяем expires
120 126
            if (!isset($data->time_expires) or ($data->time_expires < microtime(true))) {
121 26
                return null;
122
            }
123
124 118
            return $data;
125
        }
126
127
        /**
128
         * Удаляем вхождение kv-хранилища
129
         *
130
         * @param string $key Название ключа
131
         */
132
        abstract function delete_value($key);
133
134
        /**
135
         * Стандартная стратегия выбора префиксов
136
         */
137 618
        function standard_prefix_strategy() {
138 618
            if (!isset($this->settings->region_type)) {
139 498
                $this->settings->region_type = self::RegionDomain;
140 166
            }
141 618
            if (isset($this->settings->prefix)) {
142 6
                $this->_prefix = $this->settings->prefix;
143
144 6
                return;
145
            }
146
147 618
            $this->_prefix = self::get_default_prefix_from_environment($this->settings->region_type);
148 612
        }
149
150
        /**
151
         * @param integer $region_type
152
         *
153
         * @return string
154
         * @throws KeyValueException
155
         */
156 618
        static function get_default_prefix_from_environment($region_type = self::RegionDomain) {
157
            switch ($region_type) {
158 618
                case self::RegionDomain:
159 540
                    return hash('sha256', preg_replace('|^(www\\.)?([a-z0-9.-]+)(\\:[0-9]+)?|', '$2',
160 540
                            strtolower(self::get_environment('host')))).'_';
161 78
                case self::RegionServer:
162 36
                    return '';
163 42
                case self::RegionFolder:
164
                    // @hint Мы используем sha256, а не sha512, потому что иначе у нас просто не хватит длины
165 36
                    return hash('sha256', strtolower(self::get_environment('root'))).'_';
166 2
                default:
167 6
                    throw new KeyValueException('Constructor settings is malformed. Region type can not be equal '.
168 6
                                                $region_type, 1);
169 2
            }
170
        }
171
172
        /**
173
         * @param string $key
174
         *
175
         * @return string
176
         * @throws KeyValueException
177
         */
178 582
        static function get_environment($key) {
179 2
            switch ($key) {
180 582
                case 'host':
181 546
                    return isset($_SERVER, $_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
182 42
                case 'root':
183 42
                    return isset($_SERVER, $_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : '';
184 2
                default:
185 6
                    throw new KeyValueException('malformed get_environment parameter', 2);
186 2
            }
187
        }
188
189
        /**
190
         * @return string
191
         */
192 138
        function get_prefix() {
193 138
            return $this->_prefix;
194
        }
195
196
        /**
197
         * Время просрочки значения, если есть
198
         *
199
         * @param string $key
200
         *
201
         * @return double|null
202
         */
203 18
        function get_expires_time($key) {
204 18
            $data = $this->get_value_full($key);
205 18
            if (is_null($data)) {
206 6
                return null;
207
            } else {
208 18
                return $data->time_expires;
209
            }
210
        }
211
212
        /**
213
         * Обновляем срок жизни записи
214
         *
215
         * @param string $key Название ключа
216
         * @param double $ttl Кол-во секунд, после которых значение будет считаться просроченным
217
         */
218 18
        function set_expires_time($key, $ttl) {
219 18
            $data = $this->get_value_full($key);
220 18
            if (is_null($data)) {
221 6
                return;
222
            }
223 18
            $this->set_value($key, $data->value, $ttl);
224 18
        }
225
226
        /**
227
         * Время задания значения
228
         *
229
         * @param string $key
230
         *
231
         * @return double|null
232
         */
233 42
        function get_change_time($key) {
234 42
            $data = $this->get_value_full($key);
235 42
            if (is_null($data)) {
236 18
                return null;
237
            } else {
238 42
                return $data->time_create;
239
            }
240
        }
241
242
        /**
243
         * Время задания значения
244
         *
245
         * @param string $key
246
         *
247
         * @return boolean
248
         */
249 90
        function is_exist($key) {
250 90
            return !is_null($this->get_value_full($key));
251
        }
252
253
        /**
254
         * @var double[][]
255
         */
256
        private static $_profiling = [];
257
258
        /**
259
         * @param double $time
260
         * @param string $class
261
         * @param string $action
262
         */
263 192
        protected static function add_profiling($time, $class, $action) {
264 192
            if (!array_key_exists($class, self::$_profiling)) {
265 42
                self::$_profiling[$class] = [$action => $time];
266 188
            } elseif (!array_key_exists($action, self::$_profiling[$class])) {
267 36
                self::$_profiling[$class][$action] = $time;
268 12
            } else {
269 180
                self::$_profiling[$class][$action] += $time;
270
            }
271 192
        }
272
273
        /**
274
         * Весь профайлинг
275
         *
276
         * @return double[]|double[][]
277
         */
278 6
        static function get_profiling() {
279
            $data = [
280 6
                'class' => [],
281 2
                'action' => [],
282 2
                'all' => 0,
283 2
            ];
284 6
            foreach (self::$_profiling as $class => &$obj) {
285 6
                if (!array_key_exists($class, $data['class'])) {
286 6
                    $data['class'][$class] = 0;
287 2
                }
288 6
                foreach ($obj as $action => &$time) {
289 6
                    if (array_key_exists($action, $data['action'])) {
290 6
                        $data['action'][$action] += $time;
291 2
                    } else {
292 6
                        $data['action'][$action] = $time;
293
                    }
294 6
                    $data['class'][$class] += $time;
295 6
                    $data['all'] += $time;
296 2
                }
297 2
            }
298
299 6
            return $data;
300
        }
301
302
        /**
303
         * Создаём мьютекс, соответствующий ключу и кладём его в _locks
304
         *
305
         * @param string $key
306
         */
307 6
        protected function create_lock($key) {
308
            // @todo Любой мьютекс
309 6
            $this->_locks[$key] = new FileMutex([
310 6
                'name' => 'ascetkey_'.$key,
311 6
                'type' => MutexInterface::SERVER,
312 2
            ]);
313 6
        }
314
315
        /**
316
         * Лочим мьютекс, соответствующий ключу
317
         *
318
         * @param string $key
319
         */
320 174
        protected function get_lock($key) {
321 174
            if (!isset($this->_locks[$key])) {
322 174
                $this->create_lock($key);
323 58
            }
324 174
            $this->_locks[$key]->get_lock();
325 174
            $this->_last_used_lock_key = $key;
326 174
        }
327
328
        /**
329
         * Снимаем мьютекс
330
         */
331 174
        protected function release_lock() {
332 174
            if (isset($this->_locks[$this->_last_used_lock_key])) {
333 174
                $this->_locks[$this->_last_used_lock_key]->release_lock();
334 58
            }
335 174
        }
336
337
        /**
338
         * Удаляем все созданные мьютексы, не удаляя сам объект Storage
339
         *
340
         * @param integer|null $minimal_count_for_delete
341
         */
342 30
        function clear_mutex_list($minimal_count_for_delete = null) {
343 30
            if (is_null($minimal_count_for_delete) or (count($this->_locks) >= $minimal_count_for_delete)) {
344 30
                $this->_locks = [];
345 10
            }
346 30
        }
347
348
        /**
349
         * PSR-16 wrapper
350
         */
351
352
        /**
353
         * @param string $key
354
         *
355
         * @throws \Psr\SimpleCache\InvalidArgumentException
356
         */
357 390
        protected function is_key_valid($key) {
358 390
            if (!is_string($key) and !is_integer($key)) {
359 144
                throw new KeyValueException('Invalid Argument');
360
            }
361 246
            if (preg_match('_[\\{\\}\\(\\)\\/\\@:]_', $key)) {
362 144
                throw new KeyValueException('Key contains forbidden symbol');
363
            }
364 102
        }
365
366
        /**
367
         * @param string $key
368
         * @param null   $default
369
         *
370
         * @return mixed
371
         * @throws \Psr\SimpleCache\InvalidArgumentException
372
         */
373 180
        function get($key, $default = null) {
374 180
            $this->is_key_valid($key);
375
376 108
            return $this->get_value($key, $default);
377
        }
378
379
        /**
380
         * @param string $key
381
         *
382
         * @return boolean
383
         * @throws \Psr\SimpleCache\InvalidArgumentException
384
         */
385 90
        function delete($key) {
386 90
            $this->is_key_valid($key);
387 18
            $this->delete_value($key);
388
389 18
            return true;
390
        }
391
392
        /**
393
         * @param string $key
394
         * @param mixed  $value
395
         * @param null   $ttl
396
         *
397
         * @return boolean
398
         * @throws \Psr\SimpleCache\InvalidArgumentException
399
         */
400 150
        function set($key, $value, $ttl = null) {
401 150
            $this->is_key_valid($key);
402
            try {
403 78
                $this->set_value($key, $value, !is_null($ttl) ? $ttl : 10 * 365.25 * 24 * 3600);
404 30
            } catch (KeyValueException $e) {
405 6
                return false;
406
            }
407
408 72
            return false;
409
        }
410
411
        /**
412
         * @codeCoverageIgnore
413
         */
414
        function clear() {
415
            // @hint It should be implemented in children
416
        }
417
418
        /**
419
         * @param iterable $keys
420
         * @param null     $default
421
         *
422
         * @return array
423
         * @throws \Psr\SimpleCache\InvalidArgumentException
424
         */
425 36
        function getMultiple($keys, $default = null) {
426 36
            $data = [];
427 36
            foreach ($keys as &$key) {
428 36
                $data[$key] = $this->get($key, $default);
429 12
            }
430
431 36
            return $data;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $data; (array) is incompatible with the return type declared by the interface Psr\SimpleCache\CacheInterface::getMultiple of type Psr\SimpleCache\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
432
        }
433
434
        /**
435
         * @param iterable $keys
436
         * @param null     $ttl
437
         *
438
         * @return boolean
439
         * @throws \Psr\SimpleCache\InvalidArgumentException
440
         */
441 36
        function setMultiple($keys, $ttl = null) {
442 36
            $u = true;
443 36
            foreach ($keys as $key => &$value) {
444 36
                if (!$this->set($key, $value, $ttl)) {
445 36
                    $u = false;
446 12
                }
447 12
            }
448
449 36
            return $u;
450
        }
451
452
        /**
453
         * @param iterable $keys
454
         *
455
         * @return boolean
456
         * @throws \Psr\SimpleCache\InvalidArgumentException
457
         */
458 108
        function deleteMultiple($keys) {
459 108
            $u = true;
460 108
            foreach ($keys as &$key) {
461 108
                if (!$this->delete($key)) {
462
                    // @codeCoverageIgnoreStart
463
                    $u = false;
464
                    // @codeCoverageIgnoreEnd
465
                }
466 36
            }
467
468 108
            return $u;
469
        }
470
471
        /**
472
         * @param string $key
473
         *
474
         * @throws \Psr\SimpleCache\InvalidArgumentException
475
         * @return boolean
476
         */
477 162
        function has($key) {
478 162
            $this->is_key_valid($key);
479
480 90
            return $this->is_exist($key);
481
        }
482
    }
483
484
?>
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...