Passed
Push — master ( 5f2cd6...f3f23a )
by duan
01:49
created

Hash::getCache()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 11
ccs 7
cts 8
cp 0.875
crap 3.0175
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Yiranzai\Dht;
4
5
/**
6
 * Class Hash
7
 * @package Yiranzai\Dht
8
 */
9
class Hash implements \JsonSerializable
10
{
11
12
    public const DEFAULT_ALGO = 'time33';
13
    public const DEFAULT_PATH = __DIR__ . DIRECTORY_SEPARATOR . 'Config' . DIRECTORY_SEPARATOR;
14
    /**
15
     * @var Filesystem
16
     */
17
    protected $file;
18
    /**
19
     * @var string
20
     */
21
    protected $cachePath = self::DEFAULT_PATH;
22
    /**
23
     * @var array
24
     */
25
    protected $guarded = ['file'];
26
    /**
27
     * @var string
28
     */
29
    private $algo = 'time33';
30
    /**
31
     * all node cache
32
     *
33
     * @var array
34
     */
35
    private $locations = [];
36
    /**
37
     * virtual node num
38
     *
39
     * @var int
40
     */
41
    private $virtualNodeNum = 24;
42
    /**
43
     * entity node cache
44
     *
45
     * @var
46
     */
47
    private $nodes = [];
48
49
    /**
50
     * Hash constructor.
51
     * @param array $config
52
     */
53 21
    public function __construct($config = [])
54
    {
55 21
        if (!empty($config)) {
56 6
            foreach ($config as $key => $item) {
57 6
                if (in_array($key, $this->guarded, true)) {
58 3
                    continue;
59
                }
60 6
                if ($key === 'algo') {
61 3
                    $this->algo($item);
62 3
                    continue;
63
                }
64 6
                $this->$key = $item;
65
            }
66
        }
67 21
        $this->file = new Filesystem();
68 21
        if (!$this->file->exists($this->cachePath)) {
69 6
            $this->file->makeDirectory($this->cachePath);
70
        }
71 21
    }
72
73
    /**
74
     * @param string $str
75
     * @return $this
76
     */
77 6
    public function algo(string $str): self
78
    {
79 6
        if ($this->isSupportHashAlgos($str)) {
80 6
            $this->algo = $str;
81
        }
82 6
        return $this;
83
    }
84
85
    /**
86
     * @param string $str
87
     * @return bool
88
     */
89 6
    public function isSupportHashAlgos(string $str): bool
90
    {
91 6
        return $str === self::DEFAULT_ALGO || in_array($str, $this->supportHashAlgos(), true);
92
    }
93
94
    /**
95
     * @return array
96
     */
97 3
    public function supportHashAlgos(): array
98
    {
99 3
        return hash_algos();
100
    }
101
102
    /**
103
     * @param string|array $data
104
     * @param string       $path
105
     */
106 3
    public static function cache($data, $path = self::DEFAULT_PATH): void
107
    {
108 3
        $file = new Filesystem();
109 3
        if (!$file->exists($path)) {
110 3
            $file->makeDirectory($path);
111
        }
112 3
        if (is_array($data)) {
113 3
            $data = json_encode($data);
114 3
        } elseif ($data instanceof \JsonSerializable) {
0 ignored issues
show
introduced by
$data is never a sub-type of JsonSerializable.
Loading history...
115 3
            $data = json_encode($data);
116
        } else {
117 3
            json_decode($data, true);
118 3
            if (json_last_error() !== JSON_ERROR_NONE) {
119
                throw new \RuntimeException('The data must be json or array');
120
            }
121
        }
122
123 3
        $file->put($path . 'config', $data);
124 3
    }
125
126
    /**
127
     * @param string $path
128
     * @return mixed
129
     */
130 6
    public static function getCache($path = self::DEFAULT_PATH)
131
    {
132 6
        $file = new Filesystem();
133 6
        if (!$file->exists($path . 'config')) {
134 3
            throw new \RuntimeException('This path already exists');
135
        }
136 3
        $data = json_decode($file->get($path . 'config'));
137 3
        if (json_last_error() !== JSON_ERROR_NONE) {
138
            throw new \RuntimeException('The data is invalid');
139
        }
140 3
        return $data;
141
    }
142
143
    /**
144
     * @return string
145
     */
146 3
    public function toJson(): string
147
    {
148 3
        $json = json_encode($this->jsonSerialize());
149 3
        if (json_last_error() !== JSON_ERROR_NONE) {
150
            throw new \RuntimeException(json_last_error_msg());
151
        }
152
153 3
        return $json;
154
    }
155
156
    /**
157
     * Convert the object into something JSON serializable.
158
     *
159
     * @return array
160
     */
161 3
    public function jsonSerialize(): array
162
    {
163 3
        return $this->toArray();
164
    }
165
166
    /**
167
     * @return array
168
     */
169 6
    public function toArray(): array
170
    {
171 6
        $array = array();
172 6
        foreach ($this as $key => $value) {
173 6
            if (in_array($key, $this->guarded, true)) {
174 6
                continue;
175
            }
176 6
            $array[$key] = $value;
177
        }
178 6
        return $array;
179
    }
180
181
    /**
182
     * @param string $str
183
     * @return int
184
     */
185 18
    public function hashGenerate(string $str): int
186
    {
187 18
        if ($this->algo === self::DEFAULT_ALGO) {
188 15
            return $this->time33($str);
189
        }
190 3
        return (int)sprintf('%u', hash($this->algo, $str));
191
    }
192
193
    /**
194
     * @param string $str
195
     * @return int
196
     */
197 15
    private function time33(string $str): int
198
    {
199 15
        $hash = 0;
200 15
        $str  = md5($str);
201 15
        $len  = 32;
202 15
        for ($i = 0; $i < $len; $i++) {
203 15
            $hash = ($hash << 5) + $hash + ord($str{$i});
204
        }
205 15
        return $hash & 0x7FFFFFFF;
206
    }
207
208
    /**
209
     * 寻找字符串所在的机器位置
210
     * @param string $key
211
     * @return bool|mixed
212
     */
213 6
    public function getLocation(string $key)
214
    {
215 6
        if (empty($this->locations)) {
216 3
            throw new \RuntimeException('This nodes is empty, please add a node');
217
        }
218
219 3
        $position = $this->hashGenerate($key);
220
        //默认取第一个节点
221 3
        $node = current($this->locations);
222 3
        foreach ($this->locations as $k => $v) {
223
            //如果当前的位置,小于或等于节点组中的一个节点,那么当前位置对应该节点
224 3
            if ($position <= $k) {
225 3
                $node = $v;
226 3
                break;
227
            }
228
        }
229 3
        return $node;
230
    }
231
232
    /**
233
     * 添加一个节点
234
     * @param string $node
235
     * @return Hash
236
     */
237 15
    public function addEntityNode(string $node): self
238
    {
239 15
        if ($this->existsNode($node)) {
240 3
            throw new \RuntimeException('This node already exists');
241
        }
242 15
        $this->nodes[$node] = [];
243
        //生成虚拟节点
244 15
        for ($i = 0; $i < $this->virtualNodeNum; $i++) {
245 15
            $tmp                   = $this->hashGenerate($node . $i);
246 15
            $this->locations[$tmp] = $node;
247 15
            $this->nodes[$node][]  = $tmp;
248
        }
249
        //对节点排序
250 15
        ksort($this->locations, SORT_NUMERIC);
251 15
        return $this;
252
    }
253
254
    /**
255
     * @param string $node
256
     * @return bool
257
     */
258 15
    public function existsNode(string $node): bool
259
    {
260 15
        return array_key_exists($node, $this->nodes);
261
    }
262
263
    /**
264
     * delete a entity node
265
     *
266
     * @param $node
267
     * @return Hash
268
     */
269 3
    public function deleteEntityNode($node): self
270
    {
271 3
        foreach ($this->nodes[$node] as $v) {
272 3
            unset($this->locations[$v]);
273
        }
274 3
        unset($this->nodes[$node]);
275 3
        return $this;
276
    }
277
278
    /**
279
     * @param string $str
280
     * @return $this
281
     */
282 3
    public function path(string $str): self
283
    {
284 3
        $this->cachePath = $str;
285 3
        return $this;
286
    }
287
288
    /**
289
     * @param int $num
290
     * @return $this
291
     */
292 3
    public function virtualNodeNum(int $num): self
293
    {
294 3
        $this->virtualNodeNum = $num;
295 3
        return $this;
296
    }
297
}
298