AbstractStorage::set()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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