如果要查找站点的托管位置或根据用户的位置为用户自定义内容,将IP地址转换为一些有用的位置信息可能会很有用。
所有这些代码都可以在github上免费获得。
有几种方法可以做到这一点,每种方法都有其优点和缺点,但是如果坚持下去,可能会导致将来重写大量代码。因此,我决定使用依赖注入来允许使用不同的类,这些类将IP地址以不同的方式转换为位置,而不是一味地坚持下去。第一个任务是创建一个抽象类,该抽象类将用于构造其余的IP位置类。扩展此抽象类的每个类都将包含一个称为getIpLocation()会将IP地址转换为位置,以及一种将更新数据源以进行位置查找的方法。我没有将所有类都集中到一个目录中,而是创建了一个名为Service的目录,该目录中将保留查找IP地址的所有不同类。
使用典型的Zend Framework命名约定,Service目录中的Abstract类将称为IpLocation_Service_Abstract。这是完整的IpLocation_Service_Abstract类。
abstract class IpLocation_Service_Abstract { /** * Convert an IP address into an integer value for data lookup. * * @param string $ip The IP address to be converted. * * @return integer The converted IP address. */ protected function convertIpToDecimal($ip) { $ip = explode(".", $ip); return ($ip[0]*16777216) + ($ip[1]*65536) + ($ip[2]*256) + ($ip[3]); } /** * Lookup an IP address and return a IpLocation_Results object containing * the data found. * * @param string $ip The IP address to lookup. * * @return boolean|IpLocation_Results The location in the form of an * IpLocation_Results object. False * if result is not found. */ abstract public function getIpLocation($ip); /** * Update IP location data. * * @return boolean True if update sucessful. Otherwise false. */ abstract public function updateData(); }
为了为我们的IP查找服务类提供一个简单的接口,我创建了一个名为IpLocation_Ip的类。此类将在调用getIpLocation()服务类内部调用的函数并返回结果之前执行一些IP验证。
class IpLocation_Ip { /** * @var string The last IP address converted. */ public $ip; /** * @var IpLocation_Results The location object. */ public $results; /** * @var IpLocation_Service_Abstract The location service to use when * converting IP addresses into locations. */ private $_ipLocationService; /** * IpLocation_Ip * * @param IpLocation_Service_Abstract $locationService The location service * to use in this lookup. */ public function IpLocation_Ip(IpLocation_Service_Abstract $locationService) { $this->_ipLocationService = $locationService; } /** * Use the location service to lookup the IP address location and return the * result object. * * @param string $ip The ip address to lookup. * * @return string The location */ public function getIpLocation($ip) { if ($this->validateIp($ip) === false) { return false; } $this->results = $this->_ipLocationService->getIpLocation($ip); return $this->results; } /** * Validate IP address * * @param string $ip The IP address * * @return boolean True if IP address is valid. */ public function validateIp($ip) { if (!preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/D', $ip)) { return false; } $octets = explode('.', $ip); for ($i = 1; $i < 5; $i++) { $octet = (int)$octets[($i-1)]; if ($i === 1) { if ($octet > 223 OR $octet < 1) { return false; } } elseif ($i === 4) { if ($octet < 1) { return false; } } else { if ($octet > 254) { return false; } } } return true; } }
最后一步是开始创建将扩展抽象类的类。这些类都将获取IP地址并返回一些数据,但是这些类将返回一个称为IpLocation_Results的标准对象,而不是返回数组。如果我们想扩展一个类返回的数据量,但保持现有功能不变,则创建该类将对将来有所帮助。这是IpLocation_Results对象。
class IpLocation_Results { /** * @var array The data of the IP conversion. */ private $_results = array( 'ip' => '', 'country2Char' => '', 'countryName' => '', ); /** * Constructor. * * @param string $ip The IP address. * @param string $country2Char 2 character code for the country. * @param string $countryName The name of the country. */ public function IpLocation_Results($ip, $country2Char, $countryName) { $this->_results['ip'] = $ip; $this->_results['country2Char'] = $country2Char; $this->_results['countryName'] = $countryName; } /** * Get value. * * @param string $name The name of the value to return * * @return null|string The value if the name is present. */ public function __get($name) { if (!isset($this->_results[$name])) { return null; } return $this->_results[$name]; } }
在IP地址和位置之间进行转换的一种常见方法是使用PEAR扩展名Geo_IP。因此,一个不错的起点是创建一个使用Geo_IP进行IP到位置转换的服务类。
class IpLocation_Service_GeoIp extends IpLocation_Service_Abstract { /** * The location of the data update file. * * @var string */ protected $updateFile = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz'; /** * The location of the data update file. * * @var string */ protected $geoIpDatFile = 'GeoIP.dat'; /** * IpLocation_Service_GeoIp */ public function IpLocation_Service_GeoIp() { } /** * Lookup an IP address and return a IpLocation_Results object containing * the data found. * * @param string $ip The ip address to lookup. * * @return string The location */ public function getIpLocation($ip) { // 创建Net_GeoIP对象。 $geoip = Net_GeoIP::getInstance( dirname(__FILE__) . '/data/' . $this->geoIpDatFile ); try { $country2Char = $geoip->lookupCountryCode($ip); $countryName = strtoupper($geoip->lookupCountryName($ip)); } catch (Exception $e) { return false; } if ($country2Char == '' || $countryName == '') { return false; } return new IpLocation_Results($ip, $country2Char, strtoupper($countryName)); } /** * Update the datafile. * * @return boolean True if file update sucessful. */ public function updateData() { $update = file_get_contents($this->updateFile); if (strlen($update) < 2) { return false; } if (!$handle = fopen('tmp.dat.gz', 'wb')) { return false; } if (fwrite($handle, $update) == false) { return false; } fclose($handle); $FileOpen = fopen('tmp.dat.gz', "rb"); fseek($FileOpen, -4, SEEK_END); $buf = fread($FileOpen, 4); $GZFileSize = end(unpack("V", $buf)); fclose($FileOpen); $gzhandle = gzopen('tmp.dat.gz', "rb"); $contents = gzread($gzhandle, $GZFileSize); gzclose($gzhandle); $fp = fopen(dirname(__FILE__) . "/data/" . $this->geoIpDatFile, 'wb'); fwrite($fp, $contents); fclose($fp); // 删除tmp文件。 unlink('tmp.dat.gz'); return true; } }
通过创建Ip_Location_Ip实例并将IP查找服务的新实例注入构造器来使用这些类。getIpLocation()如果提供了格式正确的IP地址,则该方法将查找该IP地址并返回包含该位置的IpLocation_Results对象,如果失败则返回false。下面的示例演示了该操作的实际效果,并将显示该google.com站点托管在美国。
$objIpLocationObject = new IpLocation_Ip(new IpLocation_Service_GeoIp()); $results = $objIpLocationObject->getIpLocation('66.102.9.105'); //google.comIP地址 print_r($results); // 打印结果对象
与其将所有服务类代码都包含在这篇文章中,不如将它们包含在文章末尾的下载中。但是,我认为复习我在这里创建的服务可能会很有用。每个服务都有一个getIpLocation()从提供的数据中查找位置的updateData()方法,以及一种将更新查找所需数据的方法。
IpLocation_Service_CsvMaxmind使用Maxmind IP定位CSV文件以查找位置。Maxmind是GeoIP.dat在Net_GeoIP软件包中提供文件使用的同一站点。与使用dat文件相比,这是一种稍慢的方法,但是它可以工作。
IpLocation_Service_CsvWebhosting Webhosting是另一个提供IP CSV文件到位置的站点。同样,此方法会稍慢一些,因为在检查包含IP位置的线路之前的每一条线路上,如果超过100,000条线路,则可能需要花费很长时间。
IpLocation_Service_Mysql此类采用由Webhosting创建的CSV并将信息插入数据库中。此类中还包括createTable()一种可用于创建IP查找表的方法。
我们可以做的最后一件事是封装iplocation类,并创建指向位置转换器的URL。这实际上只是将URL转换为IP地址,然后创建一个IpLocation_Ip对象进行查找。
class IpLocation_Url { /** * @var string The last IP address converted. */ public $domain; /** * * @var string The IP address being looke up. */ public $ip; /** * @var IpLocation_Service_Abstract The location service to use when * converting IP addresses into locations. */ private $_ipLocationService; /** * IpLocation_Url * * @param IpLocation_Service_Abstract $locationService The location service * to use in this lookup. */ public function IpLocation_Url(IpLocation_Service_Abstract $locationService) { $this->_ipLocationService = $locationService; } /** * Convert a URL to a IP address. * * @param string $url The URL being converted. * * @return string The IP address converted from the URL. */ public function convertDomainToIp($url) { $this->domain = $this->getDomainNameFromUrl($url); $this->ip = gethostbyname($this->domain); return $this->ip; } /** * Get just the domain name from the URL. * * @param string $url The URL to extract the domain from. * * @return string The domain name. */ public function getDomainNameFromUrl($url) { $tmp = parse_url($url); $domain = $tmp['host']; return $domain; } /** * Get the location from a given URL using the _ipLocationService object. * Returns falue if no result found or URL is invalid. * * @param string $url The URL to convert. * * @return boolean|IpLocation_Results The IpLocation_Results results * object. Returns false if no results. */ public function getUrlLocation($url) { if (!filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)) { return false; }; $ip = $this->convertDomainToIp($url); $objIpLocationObject = new IpLocation_Ip($this->_ipLocationService); return $objIpLocationObject->getIpLocation($ip); } }
所有这些代码以及单元测试都可以在github上免费获得,并且可以作为zip包下载。随意创建自己的IP查找服务类,并将它们添加到此库中。