PHP IP到位置

如果要查找站点的托管位置或根据用户的位置为用户自定义内容,将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查找服务类,并将它们添加到此库中。