1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace IpInfo; |
4
|
|
|
|
5
|
|
|
use IpInfo\Exceptions\DatabaseNotExistException; |
6
|
|
|
use IpInfo\Exceptions\InvalidDatabaseFileException; |
7
|
|
|
use IpInfo\Exceptions\IpIllegalException; |
8
|
|
|
|
9
|
|
|
class IpLocalQuery extends IpQueryContract |
10
|
|
|
{ |
11
|
|
|
private static $instance = NULL; |
12
|
|
|
|
13
|
|
|
public $encoding = 'UTF-8'; |
14
|
|
|
|
15
|
|
|
protected $file; |
16
|
|
|
private $offset; |
17
|
|
|
private $fp; |
18
|
|
|
private $index; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* 静态工厂方法,返还此类的唯一实例 |
22
|
|
|
*/ |
23
|
|
|
public static function create() |
24
|
|
|
{ |
25
|
|
|
if (is_null(self::$instance)) { |
26
|
|
|
self::$instance = new static(); |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
return self::$instance; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* 防止用户克隆实例 |
34
|
|
|
*/ |
35
|
|
|
public function __clone() |
36
|
|
|
{ |
37
|
|
|
die('Clone is not allowed.' . E_USER_ERROR); |
|
|
|
|
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
private function __construct() |
41
|
|
|
{ |
42
|
|
|
$this->openDataBase(realpath('data/ip_data.dat')); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
public function __destruct() |
46
|
|
|
{ |
47
|
|
|
if ($this->fp) { |
48
|
|
|
fclose($this->fp); |
49
|
|
|
} |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
public function query($ip) |
53
|
|
|
{ |
54
|
|
|
$this->ip = $ip; |
55
|
|
|
if (!$this->ipCheck()) { |
56
|
|
|
throw new IpIllegalException('Illegal IP:' . $this->ip); |
57
|
|
|
} |
58
|
|
|
$nip = gethostbyname($this->ip); |
59
|
|
|
$ipdot = explode('.', $nip); |
60
|
|
|
|
61
|
|
|
$nip2 = pack('N', ip2long($nip)); |
62
|
|
|
|
63
|
|
|
$tmp_offset = (int)$ipdot[0] * 4; |
64
|
|
|
$start = unpack('Vlen', $this->index[$tmp_offset] . $this->index[$tmp_offset + 1] . $this->index[$tmp_offset + 2] . $this->index[$tmp_offset + 3]); |
65
|
|
|
|
66
|
|
|
$index_offset = $index_length = NULL; |
67
|
|
|
$max_comp_len = $this->offset['len'] - 1024 - 4; |
68
|
|
|
for ($start = $start['len'] * 8 + 1024; $start < $max_comp_len; $start += 8) { |
69
|
|
|
if ($this->index{$start} . $this->index{$start + 1} . $this->index{$start + 2} . $this->index{$start + 3} >= $nip2) { |
70
|
|
|
$index_offset = unpack('Vlen', $this->index{$start + 4} . $this->index{$start + 5} . $this->index{$start + 6} . "\x0"); |
71
|
|
|
$index_length = unpack('Clen', $this->index{$start + 7}); |
72
|
|
|
|
73
|
|
|
break; |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
if ($index_offset === NULL) { |
78
|
|
|
return 'N/A'; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
fseek($this->fp, $this->offset['len'] + $index_offset['len'] - 1024); |
82
|
|
|
|
83
|
|
|
$data = explode("\t", fread($this->fp, $index_length['len'])); |
84
|
|
|
|
85
|
|
|
return [ |
86
|
|
|
'ip' => $this->ip, |
87
|
|
|
'country' => array_get($data, 0, ''), |
88
|
|
|
'area' => '', |
89
|
|
|
'region' => array_get($data, 0, '') !== array_get($data, 1, '') ? array_get($data, 1, '') : '', |
90
|
|
|
'city' => array_get($data, 2, ''), |
91
|
|
|
'county' => array_get($data, 3, ''), |
92
|
|
|
'isp' => '', |
93
|
|
|
]; |
94
|
|
|
|
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
private function openDataBase($file) |
98
|
|
|
{ |
99
|
|
|
if (!file_exists($file) || !is_readable($file)) { |
100
|
|
|
throw new DatabaseNotExistException($file . ' does not exist, or is not readable'); |
101
|
|
|
} |
102
|
|
|
$this->file = $file; |
103
|
|
|
$this->fp = fopen($file, 'rb'); |
104
|
|
|
$this->offset = unpack('Nlen', fread($this->fp, 4)); |
105
|
|
|
if ($this->offset['len'] < 4) { |
106
|
|
|
throw new InvalidDatabaseFileException('Invalid Database File!'); |
107
|
|
|
} |
108
|
|
|
$this->index = fread($this->fp, $this->offset['len'] - 4); |
109
|
|
|
} |
110
|
|
|
} |
An exit expression should only be used in rare cases. For example, if you write a short command line script.
In most cases however, using an
exit
expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.