Completed
Push — master ( 7cc92d...45b5a0 )
by zhu
01:57
created

Ip::readNode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php namespace Zhuzhichao\IpLocationZh;
2
3
use Exception;
4
5
class Ip
6
{
7
    const IPV4 = 1;
8
    const IPV6 = 2;
9
10
    private $file = NULL;
11
    private $fileSize = 0;
12
    private $nodeCount = 0;
13
    private $nodeOffset = 0;
14
15
    private $meta = [];
16
17
    private $database = __DIR__ . '/20180702.ipdb';
18
19
    private static $reader = null;
20
21
    private static $cached = [];
22
23
    /**
24
     * 查询 IP 信息
25
     *
26
     * @param $ip
27
     * @return mixed|string
28
     */
29
    public static function find($ip)
30
    {
31
        if (empty($ip) === true) {
32
            return 'N/A';
33
        }
34
35
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6) === FALSE) {
36
            throw new \InvalidArgumentException("The value \"$ip\" is not a valid IP address.");
37
        }
38
39
        $nip = gethostbyname($ip);
40
        if (isset(self::$cached[$nip]) === true) {
41
            return self::$cached[$nip];
42
        }
43
44
        try {
45
            $reader = self::init();
46
            $node   = $reader->findNode($ip);
47
48
            if ($node > 0) {
49
                $data               = $reader->resolve($node);
50
                $values             = explode("\t", $data);
51
                $location           = array_slice($values, $reader->meta['languages']['CN'], count($reader->meta['fields']));
52
                $location[]         = '';
53
                $locationCode       = self::getLocationCode($location);
54
                $location[]         = $locationCode;
55
                self::$cached[$nip] = $location;
56
57
                return self::$cached[$nip];
58
            }
59
        } catch (\Exception $e) {
60
            return $e->getMessage();
61
        }
62
63
        return 'N/A';
64
65
    }
66
67
    /**
68
     * @param $ip
69
     * @return int
70
     * @throws \Exception
71
     */
72
    private function findNode($ip)
73
    {
74
        static $v4offset = 0;
75
        static $v6offsetCache = [];
76
77
        $binary   = inet_pton($ip);
78
        $bitCount = strlen($binary) * 8; // 32 | 128
79
        $key      = substr($binary, 0, 2);
80
        $node     = 0;
81
        $index    = 0;
82
        if ($bitCount === 32) {
83
            if ($v4offset === 0) {
84
                for ($i = 0; $i < 96 && $node < $this->nodeCount; $i++) {
85
                    if ($i >= 80) {
86
                        $idx = 1;
87
                    } else {
88
                        $idx = 0;
89
                    }
90
                    $node = $this->readNode($node, $idx);
91
                    if ($node > $this->nodeCount) {
92
                        return 0;
93
                    }
94
                }
95
                $v4offset = $node;
96
            } else {
97
                $node = $v4offset;
98
            }
99
        } else {
100
            if (isset($v6offsetCache[$key])) {
101
                $index = 16;
102
                $node  = $v6offsetCache[$key];
103
            }
104
        }
105
106
        for ($i = $index; $i < $bitCount; $i++) {
107
            if ($node >= $this->nodeCount) {
108
                break;
109
            }
110
111
            $node = $this->readNode($node, 1 & ((0xFF & ord($binary[$i >> 3])) >> 7 - ($i % 8)));
112
113
            if ($i == 15) {
114
                $v6offsetCache[$key] = $node;
115
            }
116
        }
117
118
        if ($node === $this->nodeCount) {
119
            return 0;
120
        } elseif ($node > $this->nodeCount) {
121
            return $node;
122
        }
123
124
        throw new \Exception("find node failed");
125
    }
126
127
128
    /**
129
     * @param $node
130
     * @param $index
131
     * @return mixed
132
     * @throws \Exception
133
     */
134
    private function readNode($node, $index)
135
    {
136
        return unpack('N', $this->read($this->file, $node * 8 + $index * 4, 4))[1];
137
    }
138
139
140
    /**
141
     * @param $node
142
     * @return mixed
143
     * @throws \Exception
144
     */
145
    private function resolve($node)
146
    {
147
        $resolved = $node - $this->nodeCount + $this->nodeCount * 8;
148
        if ($resolved >= $this->fileSize) {
149
            return NULL;
150
        }
151
152
        $bytes = $this->read($this->file, $resolved, 2);
153
        $size  = unpack('N', str_pad($bytes, 4, "\x00", STR_PAD_LEFT))[1];
154
155
        $resolved += 2;
156
157
        return $this->read($this->file, $resolved, $size);
158
    }
159
160
    public function close()
161
    {
162
        if (is_resource($this->file) === TRUE) {
163
            fclose($this->file);
164
        }
165
    }
166
167
    /**
168
     * @param $stream
169
     * @param $offset
170
     * @param $length
171
     * @return bool|string
172
     * @throws \Exception
173
     */
174
    private function read($stream, $offset, $length)
175
    {
176
        if ($length > 0) {
177
            if (fseek($stream, $offset + $this->nodeOffset) === 0) {
178
                $value = fread($stream, $length);
179
                if (strlen($value) === $length) {
180
                    return $value;
181
                }
182
            }
183
184
            throw new \Exception("The Database file read bad data");
185
        }
186
187
        return '';
188
    }
189
190
    /**
191
     * 初始化单例
192
     *
193
     * @return Ip|null
194
     * @throws Exception
195
     */
196
    private static function init()
197
    {
198
        if (self::$reader) {
199
            return self::$reader;
200
        }
201
202
        $reader = new self();
203
        if (is_readable($reader->database) === FALSE) {
204
            throw new \InvalidArgumentException("The IP Database file \"{$reader->database}\" does not exist or is not readable.");
205
        }
206
        $reader->file = @fopen($reader->database, 'rb');
207
        if ($reader->file === FALSE) {
208
            throw new \InvalidArgumentException("IP Database File opening \"{$reader->database}\".");
209
        }
210
        $reader->fileSize = @filesize($reader->database);
211
        if ($reader->fileSize === FALSE) {
212
            throw new \UnexpectedValueException("Error determining the size of \"{$reader->database}\".");
213
        }
214
215
        $metaLength = unpack('N', fread($reader->file, 4))[1];
216
        $text       = fread($reader->file, $metaLength);
217
218
        $reader->meta = json_decode($text, true);
0 ignored issues
show
Documentation Bug introduced by
It seems like json_decode($text, true) of type * is incompatible with the declared type array of property $meta.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
219
220
        if (!isset($reader->meta['fields']) || !isset($reader->meta['languages'])) {
221
            throw new \Exception('IP Database metadata error.');
222
        }
223
224
        $fileSize = 4 + $metaLength + $reader->meta['total_size'];
225
        if ($fileSize != $reader->fileSize) {
226
            throw  new \Exception('IP Database size error.');
227
        }
228
229
        $reader->nodeCount  = $reader->meta['node_count'];
230
        $reader->nodeOffset = 4 + $metaLength;
231
232
        self::$reader = $reader;
233
234
        return $reader;
235
    }
236
237
    /**
238
     * 获取城市的行政区划编码
239
     *
240
     * @param $arr
241
     * @return string
242
     */
243
    private static function getLocationCode($arr)
244
    {
245
        $province     = $arr[1];
246
        $city         = $arr[2];
247
        $locationCode = self::locations();
248
        $code         = "";
249
        if (!isset($locationCode[$province])) {
250
            return $code;
251
        }
252
        $code = $locationCode[$province]["code"];
253
        if (!empty($city)) {
254
            foreach ($locationCode[$province]["cities"] as $key => $loc) {
255
                if (strpos($key, $city) !== false) {
256
                    $code = $loc;
257
                    break;
258
                }
259
            }
260
        }
261
262
        return $code;
263
    }
264
265
    /**
266
     * 城市的行政区划信息
267
     *
268
     * @return array
269
     */
270
    public static function locations()
271
    {
272
        $locationCode        = [];
273
        $locationCode["北京"]  = [
274
            "code"   => "110000",
275
            "cities" => [],
276
        ];
277
        $locationCode["天津"]  = [
278
            "code"   => "120000",
279
            "cities" => [],
280
        ];
281
        $locationCode["河北"]  = [
282
            "code"   => "130000",
283
            "cities" => ["石家庄市" => "130100", "唐山市" => "130200", "秦皇岛市" => "130300", "邯郸市" => "130400", "邢台市" => "130500", "保定市" => "130600", "张家口市" => "130700", "承德市" => "130800", "沧州市" => "130900", "廊坊市" => "131000", "衡水市" => "131100"],
284
        ];
285
        $locationCode["山西"]  = [
286
            "code"   => "140000",
287
            "cities" => ["太原市" => "140100", "大同市" => "140200", "阳泉市" => "140300", "长治市" => "140400", "晋城市" => "140500", "朔州市" => "140600", "晋中市" => "140700", "运城市" => "140800", "忻州市" => "140900", "临汾市" => "141000", "吕梁市" => "141100"],
288
        ];
289
        $locationCode["内蒙古"] = [
290
            "code"   => "150000",
291
            "cities" => ["呼和浩特市" => "150100", "包头市" => "150200", "乌海市" => "150300", "赤峰市" => "150400", "通辽市" => "150500", "鄂尔多斯市" => "150600", "呼伦贝尔市" => "150700", "巴彦淖尔市" => "150800", "乌兰察布市" => "150900", "兴安盟" => "152200", "锡林郭勒盟" => "152500", "阿拉善盟" => "152900"],
292
        ];
293
        $locationCode["辽宁"]  = [
294
            "code"   => "210000",
295
            "cities" => ["沈阳市" => "210100", "大连市" => "210200", "鞍山市" => "210300", "抚顺市" => "210400", "本溪市" => "210500", "丹东市" => "210600", "锦州市" => "210700", "营口市" => "210800", "阜新市" => "210900", "辽阳市" => "211000", "盘锦市" => "211100", "铁岭市" => "211200", "朝阳市" => "211300", "葫芦岛市" => "211400"],
296
        ];
297
        $locationCode["吉林"]  = [
298
            "code"   => "220000",
299
            "cities" => ["长春市" => "220100", "吉林市" => "220200", "四平市" => "220300", "辽源市" => "220400", "通化市" => "220500", "白山市" => "220600", "松原市" => "220700", "白城市" => "220800", "延边朝鲜族自治州" => "222400"],
300
        ];
301
        $locationCode["黑龙江"] = [
302
            "code"   => "230000",
303
            "cities" => ["哈尔滨市" => "230100", "齐齐哈尔市" => "230200", "鸡西市" => "230300", "鹤岗市" => "230400", "双鸭山市" => "230500", "大庆市" => "230600", "伊春市" => "230700", "佳木斯市" => "230800", "七台河市" => "230900", "牡丹江市" => "231000", "黑河市" => "231100", "绥化市" => "231200", "大兴安岭地区" => "232700"],
304
        ];
305
        $locationCode["上海"]  = [
306
            "code"   => "310000",
307
            "cities" => [],
308
        ];
309
        $locationCode["江苏"]  = [
310
            "code"   => "320000",
311
            "cities" => ["南京市" => "320100", "无锡市" => "320200", "徐州市" => "320300", "常州市" => "320400", "苏州市" => "320500", "南通市" => "320600", "连云港市" => "320700", "淮安市" => "320800", "盐城市" => "320900", "扬州市" => "321000", "镇江市" => "321100", "泰州市" => "321200", "宿迁市" => "321300"],
312
        ];
313
        $locationCode["浙江"]  = [
314
            "code"   => "330000",
315
            "cities" => ["杭州市" => "330100", "宁波市" => "330200", "温州市" => "330300", "嘉兴市" => "330400", "湖州市" => "330500", "绍兴市" => "330600", "金华市" => "330700", "衢州市" => "330800", "舟山市" => "330900", "台州市" => "331000", "丽水市" => "331100"],
316
        ];
317
        $locationCode["安徽"]  = [
318
            "code"   => "340000",
319
            "cities" => ["合肥市" => "340100", "芜湖市" => "340200", "蚌埠市" => "340300", "淮南市" => "340400", "马鞍山市" => "340500", "淮北市" => "340600", "铜陵市" => "340700", "安庆市" => "340800", "黄山市" => "341000", "滁州市" => "341100", "阜阳市" => "341200", "宿州市" => "341300", "巢湖市" => "341400", "六安市" => "341500", "亳州市" => "341600", "池州市" => "341700", "宣城市" => "341800"],
320
        ];
321
        $locationCode["福建"]  = [
322
            "code"   => "350000",
323
            "cities" => ["福州市" => "350100", "厦门市" => "350200", "莆田市" => "350300", "三明市" => "350400", "泉州市" => "350500", "漳州市" => "350600", "南平市" => "350700", "龙岩市" => "350800", "宁德市" => "350900"],
324
        ];
325
        $locationCode["江西"]  = [
326
            "code"   => "360000",
327
            "cities" => ["南昌市" => "360100", "景德镇市" => "360200", "萍乡市" => "360300", "九江市" => "360400", "新余市" => "360500", "鹰潭市" => "360600", "赣州市" => "360700", "吉安市" => "360800", "宜春市" => "360900", "抚州市" => "361000", "上饶市" => "361100"],
328
        ];
329
        $locationCode["山东"]  = [
330
            "code"   => "370000",
331
            "cities" => ["济南市" => "370100", "青岛市" => "370200", "淄博市" => "370300", "枣庄市" => "370400", "东营市" => "370500", "烟台市" => "370600", "潍坊市" => "370700", "济宁市" => "370800", "泰安市" => "370900", "威海市" => "371000", "日照市" => "371100", "莱芜市" => "371200", "临沂市" => "371300", "德州市" => "371400", "聊城市" => "371500", "滨州市" => "371600", "菏泽市" => "371700"],
332
        ];
333
        $locationCode["河南"]  = [
334
            "code"   => "410000",
335
            "cities" => ["郑州市" => "410100", "开封市" => "410200", "洛阳市" => "410300", "平顶山市" => "410400", "安阳市" => "410500", "鹤壁市" => "410600", "新乡市" => "410700", "焦作市" => "410800", "濮阳市" => "410900", "许昌市" => "411000", "漯河市" => "411100", "三门峡市" => "411200", "南阳市" => "411300", "商丘市" => "411400", "信阳市" => "411500", "周口市" => "411600", "驻马店市" => "411700", "济源市" => "419001"],
336
        ];
337
        $locationCode["湖北"]  = [
338
            "code"   => "420000",
339
            "cities" => ["武汉市" => "420100", "黄石市" => "420200", "十堰市" => "420300", "宜昌市" => "420500", "襄樊市" => "420600", "鄂州市" => "420700", "荆门市" => "420800", "孝感市" => "420900", "荆州市" => "421000", "黄冈市" => "421100", "咸宁市" => "421200", "随州市" => "421300", "恩施土家族苗族自治州" => "422800", "仙桃市" => "429004", "潜江市" => "429005", "天门市" => "429006", "神农架林区" => "429021"],
340
        ];
341
        $locationCode["湖南"]  = [
342
            "code"   => "430000",
343
            "cities" => ["长沙市" => "430100", "株洲市" => "430200", "湘潭市" => "430300", "衡阳市" => "430400", "邵阳市" => "430500", "岳阳市" => "430600", "常德市" => "430700", "张家界市" => "430800", "益阳市" => "430900", "郴州市" => "431000", "永州市" => "431100", "怀化市" => "431200", "娄底市" => "431300", "湘西土家族苗族自治州" => "433100"],
344
        ];
345
        $locationCode["广东"]  = [
346
            "code"   => "440000",
347
            "cities" => ["广州市" => "440100", "韶关市" => "440200", "深圳市" => "440300", "珠海市" => "440400", "汕头市" => "440500", "佛山市" => "440600", "江门市" => "440700", "湛江市" => "440800", "茂名市" => "440900", "肇庆市" => "441200", "惠州市" => "441300", "梅州市" => "441400", "汕尾市" => "441500", "河源市" => "441600", "阳江市" => "441700", "清远市" => "441800", "东莞市" => "441900", "中山市" => "442000", "潮州市" => "445100", "揭阳市" => "445200", "云浮市" => "445300"],
348
        ];
349
        $locationCode["广西"]  = [
350
            "code"   => "450000",
351
            "cities" => ["南宁市" => "450100", "柳州市" => "450200", "桂林市" => "450300", "梧州市" => "450400", "北海市" => "450500", "防城港市" => "450600", "钦州市" => "450700", "贵港市" => "450800", "玉林市" => "450900", "百色市" => "451000", "贺州市" => "451100", "河池市" => "451200", "来宾市" => "451300", "崇左市" => "451400"],
352
        ];
353
        $locationCode["海南"]  = [
354
            "code"   => "460000",
355
            "cities" => ["海口市" => "460100", "三亚市" => "460200", "五指山市" => "469001", "琼海市" => "469002", "儋州市" => "469003", "文昌市" => "469005", "万宁市" => "469006", "东方市" => "469007", "定安县" => "469021", "屯昌县" => "469022", "澄迈县" => "469023", "临高县" => "469024", "白沙黎族自治县" => "469025", "昌江黎族自治县" => "469026", "乐东黎族自治县" => "469027", "陵水黎族自治县" => "469028", "保亭黎族苗族自治县" => "469029", "琼中黎族苗族自治县" => "469030", "西沙群岛" => "469031", "南沙群岛" => "469032", "中沙群岛的岛礁及其海域" => "469033"],
356
        ];
357
        $locationCode["重庆"]  = [
358
            "code"   => "500000",
359
            "cities" => [],
360
        ];
361
        $locationCode["四川"]  = [
362
            "code"   => "510000",
363
            "cities" => ["成都市" => "510100", "自贡市" => "510300", "攀枝花市" => "510400", "泸州市" => "510500", "德阳市" => "510600", "绵阳市" => "510700", "广元市" => "510800", "遂宁市" => "510900", "内江市" => "511000", "乐山市" => "511100", "南充市" => "511300", "眉山市" => "511400", "宜宾市" => "511500", "广安市" => "511600", "达州市" => "511700", "雅安市" => "511800", "巴中市" => "511900", "资阳市" => "512000", "阿坝藏族羌族自治州" => "513200", "甘孜藏族自治州" => "513300", "凉山彝族自治州" => "513400"],
364
        ];
365
        $locationCode["贵州"]  = [
366
            "code"   => "520000",
367
            "cities" => ["贵阳市" => "520100", "六盘水市" => "520200", "遵义市" => "520300", "安顺市" => "520400", "铜仁地区" => "522200", "黔西南布依族苗族自治州" => "522300", "毕节地区" => "522400", "黔东南苗族侗族自治州" => "522600", "黔南布依族苗族自治州" => "522700"],
368
        ];
369
        $locationCode["云南"]  = [
370
            "code"   => "530000",
371
            "cities" => ["昆明市" => "530100", "曲靖市" => "530300", "玉溪市" => "530400", "保山市" => "530500", "昭通市" => "530600", "丽江市" => "530700", "普洱市" => "530800", "临沧市" => "530900", "楚雄彝族自治州" => "532300", "红河哈尼族彝族自治州" => "532500", "文山壮族苗族自治州" => "532600", "西双版纳傣族自治州" => "532800", "大理白族自治州" => "532900", "德宏傣族景颇族自治州" => "533100", "怒江傈僳族自治州" => "533300", "迪庆藏族自治州" => "533400"],
372
        ];
373
        $locationCode["西藏"]  = [
374
            "code"   => "540000",
375
            "cities" => ["拉萨市" => "540100", "昌都地区" => "542100", "山南地区" => "542200", "日喀则地区" => "542300", "那曲地区" => "542400", "阿里地区" => "542500", "林芝地区" => "542600"],
376
        ];
377
        $locationCode["陕西"]  = [
378
            "code"   => "610000",
379
            "cities" => ["西安市" => "610100", "铜川市" => "610200", "宝鸡市" => "610300", "咸阳市" => "610400", "渭南市" => "610500", "延安市" => "610600", "汉中市" => "610700", "榆林市" => "610800", "安康市" => "610900", "商洛市" => "611000"],
380
        ];
381
        $locationCode["甘肃"]  = [
382
            "code"   => "620000",
383
            "cities" => ["兰州市" => "620100", "嘉峪关市" => "620200", "金昌市" => "620300", "白银市" => "620400", "天水市" => "620500", "武威市" => "620600", "张掖市" => "620700", "平凉市" => "620800", "酒泉市" => "620900", "庆阳市" => "621000", "定西市" => "621100", "陇南市" => "621200", "临夏回族自治州" => "622900", "甘南藏族自治州" => "623000"],
384
        ];
385
        $locationCode["青海"]  = [
386
            "code"   => "630000",
387
            "cities" => ["西宁市" => "630100", "海东地区" => "632100", "海北藏族自治州" => "632200", "黄南藏族自治州" => "632300", "海南藏族自治州" => "632500", "果洛藏族自治州" => "632600", "玉树藏族自治州" => "632700", "海西蒙古族藏族自治州" => "632800"],
388
        ];
389
        $locationCode["宁夏"]  = [
390
            "code"   => "640000",
391
            "cities" => ["银川市" => "640100", "石嘴山市" => "640200", "吴忠市" => "640300", "固原市" => "640400", "中卫市" => "640500"],
392
        ];
393
        $locationCode["新疆"]  = [
394
            "code"   => "650000",
395
            "cities" => ["乌鲁木齐市" => "650100", "克拉玛依市" => "650200", "吐鲁番地区" => "652100", "哈密地区" => "652200", "昌吉回族自治州" => "652300", "博尔塔拉蒙古自治州" => "652700", "巴音郭楞蒙古自治州" => "652800", "阿克苏地区" => "652900", "克孜勒苏柯尔克孜自治州" => "653000", "喀什地区" => "653100", "和田地区" => "653200", "伊犁哈萨克自治州" => "654000", "塔城地区" => "654200", "阿勒泰地区" => "654300", "石河子市" => "659001", "阿拉尔市" => "659002", "图木舒克市" => "659003", "五家渠市" => "659004"],
396
        ];
397
398
        return $locationCode;
399
    }
400
}
401