How To Make An IP-To-Country Tool With PHP and MySQL
When it comes to creating web applications, there is no guarantee where your audience will hail from. You might be in sunny California, but in today’s global community it is almost certain that an English-language application will attract visitors from Canada, the UK, Australia, and probably many other countries as well.
With that in mind, it would be nice to offer your users location-sensitive content. This is true whether your app is built around country-specific features (a shopping site, for example) or simply for pre-filling the country and timezone dropdowns on your registration form.
In this tutorial, we will create a database that allows us to map an IP address to a country, and use that information to serve up location-specific content.
Creating the database
Your database structure will depend, in part, on the data you intend to use to populate it. I am using the free IP to Country database from Webnet77 so my structure is based on that file, but most (if not all) ip-to-country datasets contain the following fields:
- A lower numeric bound
- An upper numeric bound
- The registry in charge of that range of IPs
- The date the IP range was last updated
- A two-character code for the country
- A three-character code for the country
- The full name of the country
In your chosen MySQL database, create the table, mapping fields to data columns in the order in which they appear:
CREATE TABLE `iptocountry` (`lower_bound` int(11) unsigned NOT NULL default '0',`upper_bound` int(11) unsigned NOT NULL default '0',`owner` varchar(20) NOT NULL default '',`last_updated` int(11) default NULL,`two_char_code` char(2) NOT NULL default '',`three_char_code` char(3) NOT NULL default '',`country` varchar(100) NOT NULL default '');
Importing the data
The raw data is in the form of a 6MB .csv file containing over 80,000 lines of data. What we will do is upload it to our site and create a PHP script to read the file into the database, one line at a time.
With such a large dataset, it’s a good idea to spit out regular status messages to make sure our script is behaving—it’s better than waiting for some sort of response and not being sure whether it’s worked or not. Normally, PHP will only write content to the page when the entire script has finished, but by using the flush() function we can force the output buffer to print any output line-by-line. I use a handy wrapper function to accomplish this:
function status($str){echo $str . '<br>';flush();}
Onto the script. We’ll upload it to the same folder as the .csv data file. First we need to connect to our MySQL database:
$db = mysql_connect('localhost', 'root', 'root') or die('Could not connect to database');mysql_select_db('iptocountry', $db) or die('Could not select database');status('Connected to database successfully');
Next we open the data file for reading, using PHP’s fopen() function:
$fh = fopen('./iptocountry.csv', 'r');status('Opened csv file for reading');
We need to loop through the data file one line at a time (ignoring any commented lines). Each line needs to be stripped of the quotes that wrap each field; split into its component parts (separated by commas); and then inserted into the table. We’ll print the MySQL query used, and also any error messages thrown.
while (!feof($fh)){$line = str_replace('"', '', fgets($fh));if (substr($line, 0, 1) != '#') // Ignore commented lines{$arr = explode(",", $line);$sql = "INSERT INTO iptocountry VALUES ('" . implode("','", $arr) . "')";mysql_query($sql);status($sql);if(mysql_error()){status(mysql_error());}}}
The handy fgets() function reads through the file returning a single line each iteration. If the line doesn’t start with a #, we explode() it around the separating commas, and insert it into the database using implode() to recreate the query. (Note that the query assumes that the data appears in the same order as our fields—if you are using a different data source than me, make sure your data and table structure match up.)
Finally (and because we’re responsible developers) we close the two connections in use:
fclose($fh);mysql_close($db);
Running the script should generate a huge page of status messages as each record is inserted into the database.
Using the data—where am I
So now we have a database that can translate an IP address into a country—how do we use it?
Your site visitor’s IP address can be retrieved from the $_SERVER collection:
$ip = $_SERVER['REMOTE_ADDR'];
To translate the four-part IP address into a single numeric value, you need to convert the a.b.c.d format into d + (256 * c) + (256 * 256 * b) + (256 * 256 * 256 * a). We can do this by splitting the IP address on the period:
$parts = explode('.', $ip);$numeric_ip = $parts[3] + (256 * $parts[2]) + (256 * 256 * $parts[1]) + (256 * 256 * 256 * $parts[0]);
Now all that remains is to query the database for the row where $numeric_ip is between the lower and upper bounds:
$db = mysql_connect('localhost', 'root', 'root') or die('Could not connect to database');mysql_select_db('iptocountry', $db) or die('Could not select database');$sql = "SELECT two_char_code FROM iptocountry WHERE lower_bound <= $numeric_ip AND upper_bound >= $numeric_ip LIMIT 1";$result = mysql_query($sql);$row = mysql_result($result, 0);
The variable $row now contains the two-character country code of your user.
Potential applications
There are plenty of ways that this information can now be used. Apart from the aforementioned pre-populating of country dropdowns, you could also:
- Select the correct translations of your content
- Include the correct static file for the country:
include(“sidebar-‘ . strtolower($row) . ‘.html”);*Use the country code to query country-specific APIs, such as Amazon’sI will be making use of the latter, using the geo-aware script to query the correct Amazon API and generate links to the relevant site for my visitors.
Maintenance
IP address allocations can change, so if you were using this technique in a mission-critical application it would be a good idea to clean and re-populate the database every now and then.
If you were particularly hardcore, I imagine it would be fairly easy to suck down the gzipped .csv file direct from the source, unzip it, and run the import script, all as a scheduled task on your server.
Technorati tags: php mysql ip country geo
Bookmark this article with del.icio.us
Previously: Staying connected to home with LogMeIn
Next: Refresh Cambridge July - PPC management
Comments
$numeric_ip = $parts3 + (256 * $parts2) + (256 * 256 * $parts1) + (256 * 256 * 256 * $parts0);
falls over handling large signed integers, use this to prevent a lot of IPs from not working:
(instead of the explode statement, use sprintf to format the number as an unsigned integer, also uses PHP’s built in ip2long function)
$numeric_ip = sprintf(”%u”,ip2long($’ip]));
Regards