IP Address to Country in PHP

AID: 3437
  • Status: Published

10268 points

  • ByRay_Paseur
  • TypeGeneral
  • Posted on2010-07-20 at 12:16:16
Awards
  • Community Pick
This article teaches how to get the client's country information by looking up the client's IP address.

The IP address can usually be found in $_SERVER["REMOTE_ADDR"].  In most PHP configurations this is reliable, but please see the man page notes about relying solely on the values in $_SERVER.
http://www.php.net/manual/en/reserved.variables.server.php

This reference may be useful, too.
http://www.php.net/manual/en/function.gethostbyname.php

1. ACQUIRE THE IP-TO-COUNTRY DATA

Our first step is to acquire a data base of IP ranges and country identifications.  You can download a CSV file with this information from MaxMind here:
http://geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip  
It will look something like this:

12.109.8.112,  12.109.8.119,    208472176,  208472183,  PR,  Puerto Rico
12.109.8.120,  12.129.198.255,  208472184,  209831679,  US,  United States
12.129.199.0,  12.129.199.31,   209831680,  209831711,  DE,  Germany

The first two fields are IP string ranges, also called the IP address.  The next two fields are the corresponding IP numbers.  The last two fields are the country abbreviation and the country name.  On July 1, 2010 the file had a bit less than 130,000 rows.  The file will be about 9MB after it is unzipped.

Unzip the file and store it on your server in the web root - let's call it 'GeoIPCountryWhois.csv' for now.  You might attach the current date to the file name, in case you are interested in keeping track of versions.  MaxMind issues a new release of the file every month.

2. LOAD THE IP-TO-COUNTRY INTO A DATA BASE

The second step is to load the data base using the contents of the CSV file.

Run the "LOAD" script from the same directory as the 'GeoIPCountryWhois.csv' data.  On my server it took about 15 seconds to run.  As you look over the code, you can see that this script does the following things.

1. We set a variable with the name of the file we downloaded from MaxMind - that is our source of input data (line 11).
2. We connect and select our data base (lines 14 - 36).
3. We discard any old version of the "ip2country" table (line 36).
4. We create a new table definition (lines 40 - 60).  Experienced data base users will notice that we have no index definitions in our CREATE TABLE statement.  Indexes improve performance during SELECT queries but are of no value (and may even slow down) INSERT statements, so we create the table without indexes, and we add them later after the data has been loaded.
5. We open and read the CSV file, performing some modest edits, and use INSERT to add each record to the "ip2country" table (lines 62 - 106).
6. Finally, we add the indexes to the table on the ip_number columns.  This will make table lookups very fast.

3. CREATE A "RESTful" WEB SERVICE

Now that our table is loaded and indexed for fast performance, we can look up IP addresses and return the country data.  We will demonstrate this ability by creating a RESTful web service.  REST services use the GET string (URL) to communicate with the service.  The input argument is the IP address and the response is a short CSV string with the country code and country name.  We could modify this service to return XML or JSON, but simplicity can be a virtue.  

Our REST service connects to the data base so it can use the table we created in the earlier step.  You may notice that the error messages are very terse - since this is a service that is designed to be simple to use, we only return one string of data.  If the string starts with the word, "ERROR" it indicates a failure.  Otherwise the string will have two fields separated by a comma: The country code and the country name.  For example, a visitor from "down under" would be identified with a CSV string that said "AU,Australia"

You can integrate this service into your web applications with a single line of code that sends the IP address to the URL of the REST script.  Here are some sample calls to the service:

// OUTPUT THE CLIENT'S COUNTRY
echo file_get_contents("path/to/service.php");

// OUTPUT THE COUNTRY THAT HAS IP#123.45.67.89
echo file_get_contents("path/to/service.php?ip=123.45.67.89");

// OUTPUT AN ERROR MESSAGE
echo file_get_contents("path/to/service.php?ip=BOGUS");


4. PULLING IT ALL TOGETHER

Now that you know how to find the client's country, you can do a number of interesting things with the information.

You might keep a record of where your site visitors come from.  Perhaps you could create a map of the world showing the countries that have visitors to your site.  (Google does this with its Analytics service).  

You could consider localization options, such as adding a national flag to the page banner, or suggesting a second language for the web site text.

You might be able to access specialized RSS feeds for weather or news based on the name or country code of your visitor's country.

You could provide links to Wikipedia articles in the appropriate language, for example (in Germany):
http://de.wikipedia.org/wiki/

You could even provide a link to the CIA fact book for that country!
<?php // LOAD SCRIPT
error_reporting(E_ALL);


// DEMONSTRATE HOW TO LOAD THE IP-TO-COUNTRY DATA FROM MAXMIND GEOLITE
// http://geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip
// ON JULY 1, 2010 THE FILE HAD 129,763 ROWS


// PUT THE NAME OF THE DOWNLOADED CSV FILE HERE
$csv = 'GeoIPCountryWhois.csv';


// CONNECTION AND SELECTION VARIABLES FOR THE DATABASE
$db_host = "localhost"; // PROBABLY THIS IS OK
$db_name = "??";        // GET THESE FROM YOUR HOSTING COMPANY
$db_user = "??";
$db_word = "??";

// OPEN A CONNECTION TO THE DATA BASE SERVER
if (!$dbcx = mysql_connect("$db_host", "$db_user", "$db_word"))
{
   $errmsg = mysql_errno() . ' ' . mysql_error();
   echo "<br/>NO DB CONNECTION: ";
   echo "<br/> $errmsg <br/>";
}

// SELECT THE MYSQL DATA BASE
if (!$db_sel = mysql_select_db($db_name, $dbcx))
{
   $errmsg = mysql_errno() . ' ' . mysql_error();
   echo "<br/>NO DB SELECTION: ";
   echo "<br/> $errmsg <br/>";
   die('NO DATA BASE');
}
// IF WE GOT THIS FAR WE CAN DO QUERIES

// REMOVE THE OLD DATA BASE TABLE, IF ANY (IGNORE ERRORS)
mysql_query('DROP TABLE ip2country');

// CREATE THE NEW TABLE
$sql 
= " CREATE TABLE ip2country "
. " ( "
. " ip_string_lo VARCHAR(16)  NOT NULL, "
. " ip_string_hi VARCHAR(16)  NOT NULL, "
. " ip_number_lo INT UNSIGNED NOT NULL, "
. " ip_number_hi INT UNSIGNED NOT NULL, "
. " country_code VARCHAR(2)   NOT NULL, "
. " country_name VARCHAR(64)  NOT NULL "
. " ) "
;
if (!$res = mysql_query($sql, $dbcx))
{
    // MAN PAGE: http://us.php.net/manual/en/function.mysql-error.php
    $errmsg = mysql_errno() . ' ' . mysql_error();
    echo "<br/>QUERY FAIL: ";
    echo "<br/>$sql <br/>";
    die($errmsg);
}

// OPEN THE CSV FILE AND LOAD THE TABLE
$fp = fopen($csv, 'r');
if (!$fp) die("FAIL: UNABLE TO OPEN $csv");

// READ EACH ROW OF THE IP AND COUNTRY INFORMATION
$kount = 0;
while (!feof($fp))
{
    $row = fgetcsv($fp);
    
    // IGNORE ERRORS
    if (!$row) continue;
    
    // COUNT VALID RECORDS
    $kount++;

    // IF COUNTRY IS LIKE Korea, Republic of - RECONSTRUCT THE NAME
    if (strpos($row[5], ','))
    {
        $x = explode(',', $row[5]);
        $row[5] = trim($x[1]) . ' ' . $x[0];
    }
    
    // CONSTRUCT AND RUN A QUERY TO INSERT THE DATA
    $sql 
    = " INSERT INTO ip2country ( ip_string_lo, ip_string_hi, ip_number_lo, ip_number_hi, country_code, country_name ) "
    . " VALUES "
    . " ( "
    . " '" . mysql_real_escape_string($row[0]) . "', "
    . " '" . mysql_real_escape_string($row[1]) . "', "
    .        mysql_real_escape_string($row[2]) . ", "
    .        mysql_real_escape_string($row[3]) . ", "
    . " '" . mysql_real_escape_string($row[4]) . "', "
    . " '" . mysql_real_escape_string($row[5]) . "' "
    . " ) "
    ;
    if (!$res = mysql_query($sql, $dbcx))
    {
        $errmsg = mysql_errno() . ' ' . mysql_error();
        echo "<br/>QUERY FAIL: ";
        echo "<br/>$sql <br/>";
        die($errmsg);
    }
}
echo "IP-TO-COUNTRY TABLE LOADED WITH " . number_format($kount) . " RECORDS<br/>";

// ALTER THE TABLE TO ADD INDEXES
mysql_query("ALTER TABLE `ip2country` ADD INDEX ( `ip_number_lo` )" );
mysql_query("ALTER TABLE `ip2country` ADD INDEX ( `ip_number_hi` )" );

// ALL DONE.
die("INDEXING COMPLETE");

                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:

Select allOpen in new window

<?php // REST
error_reporting(E_ALL);


// DEMONSTRATE HOW TO USE THE IP-TO-COUNTRY DATA FROM MAXMIND GEOLITE
// A RESTful SERVICE TO LOOK UP THE COUNTRY INFORMATION FOR A GIVEN IP ADDRESS
// http://www.maxmind.com/app/geoip_country
// ON JULY 1, 2010 THE FILE HAD 129,763 ROWS


// CONNECTION AND SELECTION VARIABLES FOR THE DATABASE
$db_host = "localhost"; // PROBABLY THIS IS OK
$db_name = "??";        // GET THESE FROM YOUR HOSTING COMPANY
$db_user = "??";
$db_word = "??";

// OPEN A CONNECTION TO THE DATA BASE SERVER
if (!$dbcx = mysql_connect("$db_host", "$db_user", "$db_word"))
{
    die("ERROR: QUERY FAILED " . mysql_errno() . ' ' . mysql_error() );
}

// SELECT THE MYSQL DATA BASE
if (!$db_sel = mysql_select_db($db_name, $dbcx))
{
    die("ERROR: QUERY FAILED " . mysql_errno() . ' ' . mysql_error() );
}
// IF WE GOT THIS FAR WE CAN DO QUERIES

// IF NO IP ADDRESS IS GIVEN, USE THE REMOTE ADDRESS OF THE CLIENT
$ip = $_SERVER["REMOTE_ADDR"];
if (!empty($_GET["ip"])) $ip = $_GET["ip"];

// CONVERT TO BINARY - SEE http://php.net/manual/en/function.ip2long.php
$x = ip2long($ip);
if ($x === FALSE) die('ERROR: INVALID IP ADDRESS');

// IP ADDRESSES ARE UNSIGNED AND MAY RETURN NEGATIVE VALUES
$ip_number = sprintf('%u', $x);

// QUERY FOR THE BINARY VALUE
$sql
= " SELECT country_code, country_name FROM ip2country WHERE $ip_number "
. " BETWEEN ip_number_lo AND ip_number_hi "
. " LIMIT 1 "
;
if (!$res = mysql_query($sql, $dbcx))
{
    die("ERROR: QUERY FAILED " . mysql_errno() . ' ' . mysql_error() );
}

// DID WE FIND ANYTHING?
$num = mysql_num_rows($res);
if (!$num) die("ERROR: NO DATA FOUND");

// YES, WE FOUND THE COUNTRY
$row = mysql_fetch_assoc($res);
die("{$row["country_code"]},{$row["country_name"]}");
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:

Select allOpen in new window

Asked On
2010-07-20 at 12:16:16ID3437
Tags

GIS

,

GPS

,

Geocode

,

Map

,

Location

,

Mobile

,

Proximity

,

GeoIP

,

PHP

Topic

PHP and Databases

Views
2260

Comments

Expert Comment

by: Medo3337 on 2010-12-16 at 11:41:12ID: 22122

Great article! very helpful, Thanks!

Expert Comment

by: adilkhan on 2011-02-20 at 00:02:11ID: 23991

This is great article.... but you cannot rely on GeoLite Database.... it is really outdated. i tried it with many tests and it was returning WRONG Country/Region. Best solution is to buy GeoCountry Database, pay them monthly fee for updates and use their COM object to intrtect with their .DAT database to return correct Geo Data.

Author Comment

by: Ray_Paseur on 2011-02-20 at 06:09:46ID: 23992

@adilkhan: To quote http://www.maxmind.com/app/geoip_country, "GeoLite Country is similar to the GeoIP Country database, but is slightly less accurate. Should you require greater accuracy, GeoIP Country is a drop-in replacement for GeoLite Country."

And to quote http://www.maxmind.com/app/country, "$50 site license fee and $12 per month of updates."

I have never seen GeoLite return the wrong country, but if greater accuracy is an important goal, then the paid version is probably a good value.  As is true with most things, you only get what you pay for!

Since this article was written in mid-2010, I have found another service that I have used with satisfactory results: http://www.geoio.com/

Expert Comment

by: adilkhan on 2011-02-22 at 00:16:31ID: 24046

Geoio looks like an API solution? it seems like they promise to provide 99.9% uptime? how is your experience with them so far?

Thanks

Author Comment

by: Ray_Paseur on 2011-02-22 at 04:03:01ID: 24051

So far so good with geoio.  My use case is very light - a private school baseball program with a world-wide audience of perhaps 200 ;-)

Add your Comment

Please Sign up or Log in to comment on this article.

Join Experts Exchange Today

Gain Access to all our Tech Resources

Get personalized answers

Ask unlimited questions

Access Proven Solutions

Search 3.2 million solutions

Read In-Depth How-To Guides

1000+ articles, demos, & tips

Watch Step by Step Tutorials

Learn direct from top tech pros

And Much More!

Your complete tech resource

See Plans and Pricing

30-day free trial. Register in 60 seconds.

Loading Advertisement...

Top PHP and Databases Experts

  1. Ray_Paseur

    16,777

    10 points yesterday

    Profile
    Rank: Savant
  2. StingRaY

    4,150

    0 points yesterday

    Profile
    Rank: Wizard
  3. Slick812

    4,100

    0 points yesterday

    Profile
    Rank: Sage
  4. DaveBaldwin

    3,350

    0 points yesterday

    Profile
    Rank: Genius
  5. Roads_Roads

    2,987

    10 points yesterday

    Profile
    Rank: Genius
  6. shinuq

    2,800

    0 points yesterday

    Profile
    Rank: Sage
  7. gwkg

    2,800

    0 points yesterday

    Profile
    Rank: Genius
  8. angelIII

    2,000

    0 points yesterday

    Profile
    Rank: Elite
  9. bportlock

    2,000

    0 points yesterday

    Profile
    Rank: Genius
  10. Kdo

    2,000

    0 points yesterday

    Profile
    Rank: Genius
  11. mankowitz

    2,000

    0 points yesterday

    Profile
    Rank: Sage
  12. alex_code

    2,000

    0 points yesterday

    Profile
    Rank: Guru
  13. deisrobinson

    2,000

    0 points yesterday

    Profile
    Rank: Master
  14. Olaf_Doschke

    2,000

    0 points yesterday

    Profile
    Rank: Sage
  15. idealws

    2,000

    0 points yesterday

    Profile
  16. leakim971

    2,000

    0 points yesterday

    Profile
    Rank: Genius
  17. bplantier

    2,000

    0 points yesterday

    Profile
  18. elvin66

    1,508

    0 points yesterday

    Profile
    Rank: Sage
  19. gyoreg

    1,500

    0 points yesterday

    Profile
    Rank: Master
  20. HarryMuffin

    1,500

    0 points yesterday

    Profile
  21. DrDamnit

    1,340

    0 points yesterday

    Profile
    Rank: Genius
  22. ingwa

    1,300

    0 points yesterday

    Profile
    Rank: Sage
  23. bingie

    1,200

    0 points yesterday

    Profile
    Rank: Guru
  24. hernst42

    1,200

    0 points yesterday

    Profile
    Rank: Genius
  25. COBOLdinosaur

    1,000

    0 points yesterday

    Profile
    Rank: Genius

Hall Of Fame