Completed
Push — master ( bade77...b1591b )
by Никита
04:42
created

AbstractStorage   C

Complexity

Total Complexity 66

Size/Duplication

Total Lines 470
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 2

Test Coverage

Coverage 99.4%

Importance

Changes 0
Metric Value
wmc 66
lcom 3
cbo 2
dl 0
loc 470
ccs 166
cts 167
cp 0.994
rs 5.7474
c 0
b 0
f 0

30 Methods

Rating   Name   Duplication   Size   Complexity  
__construct() 0 1 ?
A get_value() 0 5 2
B form_datum_value() 0 16 5
set_value() 0 1 ?
get_value_full_clear() 0 1 ?
A get_value_full() 0 15 4
delete_value() 0 1 ?
A standard_prefix_strategy() 0 12 3
A get_default_prefix_from_environment() 0 15 4
B get_environment() 0 10 5
A get_prefix() 0 3 1
A get_expires_time() 0 8 2
A set_expires_time() 0 7 2
A get_change_time() 0 8 2
A is_exist() 0 3 1
A add_profiling() 0 9 3
B get_profiling() 0 23 5
A create_lock() 0 7 1
A get_lock() 0 7 2
A release_lock() 0 5 2
A clear_mutex_list() 0 5 3
A is_key_valid() 0 8 4
A get() 0 5 1
A delete() 0 6 1
A set() 0 10 3
A clear() 0 3 1
A getMultiple() 0 8 2
A setMultiple() 0 10 3
A deleteMultiple() 0 12 3
A has() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractStorage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractStorage, and based on these observations, apply Extract Interface, too.

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