Completed
Push — master ( fe24a8...d41376 )
by Никита
05:34
created

AbstractStorage::standard_prefix_strategy()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

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