Completed
Push — master ( 654b22...5f2cd6 )
by duan
02:17
created

Hash::getLocation()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 4
nop 1
dl 0
loc 17
ccs 10
cts 10
cp 1
crap 4
rs 9.9666
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 18
    public function __construct($config = [])
54
    {
55 18
        if (!empty($config)) {
56 3
            foreach ($config as $key => $item) {
57 3
                if (in_array($key, $this->guarded, true)) {
58
                    continue;
59
                }
60 3
                if ($key === 'algo') {
61 3
                    $this->algo($item);
62 3
                    continue;
63
                }
64 3
                $this->$key = $item;
65
            }
66
        }
67 18
        $this->file = new Filesystem();
68 18
        if (!$this->file->exists($this->cachePath)) {
69 3
            $this->file->makeDirectory($this->cachePath);
70
        }
71 18
    }
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
            $file->makeDirectory($path);
111
        }
112 3
        if (is_array($data)) {
113 3
            $data = json_encode($data);
114
        } else {
115
            json_decode($data, true);
116
            if (json_last_error() !== JSON_ERROR_NONE) {
117
                throw new \RuntimeException('The data must be json or array');
118
            }
119
        }
120
121 3
        $file->put($path . 'config', $data);
122 3
    }
123
124
    /**
125
     * @param string $path
126
     * @return mixed
127
     */
128 3
    public static function getCache($path = self::DEFAULT_PATH)
129
    {
130 3
        $file = new Filesystem();
131 3
        if (!$file->exists($path . 'config')) {
132
            throw new \RuntimeException('This path already exists');
133
        }
134 3
        $data = json_decode($file->get($path . 'config'));
135 3
        if (json_last_error() !== JSON_ERROR_NONE) {
136
            throw new \RuntimeException('The data is invalid');
137
        }
138 3
        return $data;
139
    }
140
141
    /**
142
     * @return string
143
     */
144
    public function toJson(): string
145
    {
146
        $json = json_encode($this->jsonSerialize());
147
        if (json_last_error() !== JSON_ERROR_NONE) {
148
            throw new \RuntimeException(json_last_error_msg());
149
        }
150
151
        return $json;
152
    }
153
154
    /**
155
     * Convert the object into something JSON serializable.
156
     *
157
     * @return array
158
     */
159
    public function jsonSerialize(): array
160
    {
161
        return $this->toArray();
162
    }
163
164
    /**
165
     * @return array
166
     */
167 3
    public function toArray(): array
168
    {
169 3
        $array = array();
170 3
        foreach ($this as $key => $value) {
171 3
            if (in_array($key, $this->guarded, true)) {
172 3
                continue;
173
            }
174 3
            $array[$key] = $value;
175
        }
176 3
        return $array;
177
    }
178
179
    /**
180
     * @param string $str
181
     * @return int
182
     */
183 15
    public function hashGenerate(string $str): int
184
    {
185 15
        if ($this->algo === self::DEFAULT_ALGO) {
186 12
            return $this->time33($str);
187
        }
188 3
        return (int)sprintf('%u', hash($this->algo, $str));
189
    }
190
191
    /**
192
     * @param string $str
193
     * @return int
194
     */
195 12
    private function time33(string $str): int
196
    {
197 12
        $hash = 0;
198 12
        $str  = md5($str);
199 12
        $len  = 32;
200 12
        for ($i = 0; $i < $len; $i++) {
201 12
            $hash = ($hash << 5) + $hash + ord($str{$i});
202
        }
203 12
        return $hash & 0x7FFFFFFF;
204
    }
205
206
    /**
207
     * 寻找字符串所在的机器位置
208
     * @param string $key
209
     * @return bool|mixed
210
     */
211 6
    public function getLocation(string $key)
212
    {
213 6
        if (empty($this->locations)) {
214 3
            throw new \RuntimeException('This nodes is empty, please add a node');
215
        }
216
217 3
        $position = $this->hashGenerate($key);
218
        //默认取第一个节点
219 3
        $node = current($this->locations);
220 3
        foreach ($this->locations as $k => $v) {
221
            //如果当前的位置,小于或等于节点组中的一个节点,那么当前位置对应该节点
222 3
            if ($position <= $k) {
223 3
                $node = $v;
224 3
                break;
225
            }
226
        }
227 3
        return $node;
228
    }
229
230
    /**
231
     * 添加一个节点
232
     * @param string $node
233
     * @return Hash
234
     */
235 12
    public function addEntityNode(string $node): self
236
    {
237 12
        if ($this->existsNode($node)) {
238 3
            throw new \RuntimeException('This node already exists');
239
        }
240 12
        $this->nodes[$node] = [];
241
        //生成虚拟节点
242 12
        for ($i = 0; $i < $this->virtualNodeNum; $i++) {
243 12
            $tmp                   = $this->hashGenerate($node . $i);
244 12
            $this->locations[$tmp] = $node;
245 12
            $this->nodes[$node][]  = $tmp;
246
        }
247
        //对节点排序
248 12
        ksort($this->locations, SORT_NUMERIC);
249 12
        return $this;
250
    }
251
252
    /**
253
     * @param string $node
254
     * @return bool
255
     */
256 12
    public function existsNode(string $node): bool
257
    {
258 12
        return array_key_exists($node, $this->nodes);
259
    }
260
261
    /**
262
     * delete a entity node
263
     *
264
     * @param $node
265
     * @return Hash
266
     */
267 3
    public function deleteEntityNode($node): self
268
    {
269 3
        foreach ($this->nodes[$node] as $v) {
270 3
            unset($this->locations[$v]);
271
        }
272 3
        unset($this->nodes[$node]);
273 3
        return $this;
274
    }
275
276
    /**
277
     * @param string $str
278
     * @return $this
279
     */
280
    public function path(string $str): self
281
    {
282
        $this->cachePath = $str;
283
        return $this;
284
    }
285
286
    /**
287
     * @param int $num
288
     * @return $this
289
     */
290
    public function virtualNodeNum(int $num): self
291
    {
292
        $this->virtualNodeNum = $num;
293
        return $this;
294
    }
295
}
296