During the past few weeks I have been working on a brand new PHP extension to provide an easy object oriented interface for the GeoIP library from MaxMind. The original module (that I did not write) had two main issues. First the directory to find the databases could not be changed except in PHP.INI before PHP loads (so not even ini_set would work). Second was that it was old school and not object oriented. Another issue (so that really makes three) is that it will throw E_WARNINGS for dumb things like records not being found. If there is no record found when you ask for it I would much rather just be handed a FALSE.
[update 2011/10/26] Official GeoIPo manual/documentation is here.
This new one is nearly complete, but I am still waiting to be accepted into PECL. However it works and I have the source available for use on GitHub. Here is a little how-to on how to set up the GeoIP database and use this module.
Why use GeoIP?
- Perhaps your project has support for English, German, and Polish languages. With GeoIP you can try to guess what country your visitor is from and if they are from a country with a language you support you can set that language default for the user. Things like that go very far towards user experience.
- If you have a website, I doubt many people can honestly tell me they are not curious what country their visitors are from. GeoIP is an important part of determining your reader demographics. You know all those maps that show dots about where visitors are from? You need GeoIP info to do that.
- And the bane of all internet users – lets say you are the owner of YouTube/Spotify/iTunes and you need to prevent a certain country from viewing/watching/listening to specific content because of license restrictions. You are going to piss people off, but I guess for legal reasons it just needs to be done.
These are only three of the many valid reasons you might need GeoIP information. Over here I have a page demoing the readout of the server and visitor’s GeoIP info, with a link to the visible source at the bottom.
Installing the Library
To install check your package managers for libgeoip. On Ubuntu you can install libgeoip1 and libgeoip-dev with apt, and on FreeBSD libgeoip can be installed from Ports at net/GeoIP. If your distro has no packages, you can compile libgeoip from source yourself.
[UPDATE 2011/10/24] Ubuntu’s libgeoip seems to be broken. While the version number is right, the actual library is not and it is missing an important function that makes all this work. I am sorry, but on Ubuntu as of now you will also have to compile libgeoip from source with ./configure --prefix=/usr
Once you have libgeoip installed you can compile php-geoipo.
Compile it yourself…
Doing it yourself, extract the source and build it like any other PHP module.
Command Line Interface (CLI):phpize
./configure
make
make install
If phpize is an unknown command that means you need to install some dev packages.
Or let PECL compile it for you…
There is also a PECL source package where it will compile and install the extension for you. You will still need to have the libgeoip preinstalled before this will work!
Command Line Interface (CLI):pecl channel-discover pecl.squirrelshaterobots.com
pecl install shr/geoipo
Installing the Databases
After installing libgeoip and php-geoipo, we still need to have the GeoIP databases installed. By default I noticed Ubuntu 11 was installing some for you, but I am unsure about how old they are. MaxMind has two databases available for free and I suggest you install both of them. The first is the country database [download] and the second is the larger and more detailed city database [download]. These databases are updated once a month, and be warned if you download them too many times they will throttle you down to 20kb/s, or block you from downloading them. You only need to download them once a month anyway, so stick to that and you will be fine.
These two databases will extract into GeoIP.dat and GeoLiteCity.dat. You need to rename GeoLiteCity.dat to GeoIPCity.dat, and then move both GeoIP and GeoIPCity into the system path. Generally this will be something like /usr/share/GeoIP in the case of my Ubuntu system and /usr/local/share/GeoIP on my FreeBSD and OSX systems. If the GeoIP folder does not exist you will need to make it, and yes it is case sensitive.
You can also copy the databases to a place local to your project if for some reason you cannot install them to the system. I will cover that in a bit.
Using GeoIP and geoipo
If we are going to use the default system installed GeoIP database, then all we have to do is create an instance of the GeoIP object. This object has one property named host, the value of this property is the hostname or IP address you wish to look up in the GeoIP database. When we create the GeoIP instance it is an option to set the host in the constructor or later.
PHP Code:<?php
$geo = new GeoIP('squirrelshaterobots.com');
// - or - //
$geo = new GeoIP;
$geo->host = 'squirrelshaterobots.com';
?>
The most basic way to fetch all the useful information from the free databases is to use the getRecord method. This will return an object with all the information available on the specified host. Any fields that are missing (the database is not always complete, or accurate) will be set to boolean false, and if no record at all was found then it will return boolean false instead of the object. There are also specific methods to just pull country codes and whatnot if you only cared about that.
PHP Code:<?php
$geo = new GeoIP('squirrelshaterobots.com');
$record = $geo->getRecord();
print_r($record);
/*// output //////////////////////////////
[bob@mckay ~]$ php test.php
stdClass Object
(
[ContinentCode] => NA
[CountryCode] => US
[CountryCode3] => USA
[CountryName] => United States
[RegionCode] => MI
[RegionName] => Michigan
[City] => East Lansing
[PostalCode] => 48823
[Latitude] => 42.762699127197
[Longitude] => -84.44270324707
[TimeZone] => America/New_York
)
////////////////////////////////////////*/
?>
The library also has internal caching, so you could also do something like this with minimal performance issues:
PHP Code:<?php
$geo = new GeoIP('squirrelshaterobots.com');
print_r(array(
'country' => $geo->getRecord()->CountryCode,
'city' => $geo->getRecord()->RegionCode,
'postal' => $geo->getRecord()->PostalCode
));
/*// output //////////////////////////////
[bob@mckay ~]$ php test.php
Array
(
[country] => US
[city] => MI
[postal] => 48823
)
////////////////////////////////////////*/
?>
You can also reuse an object to look up different hosts by changing the value of the host property.
PHP Code:<?php
$sites = array(
'de.pool.ntp.org',
'it.pool.ntp.org',
'pl.pool.ntp.org',
'jp.pool.ntp.org',
'th.pool.ntp.org',
'us.pool.ntp.org',
'ca.pool.ntp.org'
);
$geo = new GeoIP;
foreach($sites as $site) {
$geo->host = $site;
print_r(array(
'site' => $geo->host,
'country' => $geo->getCountryName()
));
}
/*// output //////////////////////////////
[bob@mckay ~]$ php test.php
Array
(
[site] => de.pool.ntp.org
[country] => Germany
)
Array
(
[site] => it.pool.ntp.org
[country] => Italy
)
Array
(
[site] => pl.pool.ntp.org
[country] => Poland
)
Array
(
[site] => jp.pool.ntp.org
[country] => Japan
)
Array
(
[site] => th.pool.ntp.org
[country] => Thailand
)
Array
(
[site] => us.pool.ntp.org
[country] => United States
)
Array
(
[site] => ca.pool.ntp.org
[country] => Canada
)
////////////////////////////////////////*/
?>
Of course you can also use it to look up the GeoIP information for a visitor to your website. The host property can be any hostname or IP address, so instead of passing it a hostname string like in these examples you would use $_SERVER['REMOTE_ADDR'] as the host to get the info for a visitor.
Custom Database Directories
If you want to use a database not in the system path, then all you need to do is set it before you use any of the GeoIP functions or classes. This is done very easily with the static method GeoIP::init() on the GeoIP class. There are also methods you can use to find out where PHP thinks it is getting databases from.
PHP Code:<?php
GeoIP::init('/Users/bob/Projects/geoipo-demo');
var_dump(array(
'country' => GeoIP::getDatabaseFile(GeoIP::COUNTRY_EDITION),
'available' => GeoIP::hasDatabase(GeoIP::COUNTRY_EDITION)
));
/*// output //////////////////////////////
[bob@mckay ~]$ php test.php
array(2) {
["country"] => string(41) "/Users/bob/Projects/geoipo-demo/GeoIP.dat"
["available"] => bool(false)
}
////////////////////////////////////////*/
?>
Note, getDatabaseFile() does not mean that the file may actually exist. That is only the file path it will try to use. To make sure it actually exists use hasDatabase(). If you are having issues getting it to open the database you can use getDatabaseFile() to make sure that it is actually trying the correct one. In order to open the databases the user (or webserver) will need to have read privileges to that directory and file.

Especially PHP over e.g. webserver implementation (nginx)
looking up data from the GeoIP.dat files is not the fastest thing in the world, but it is also not terrible. this extension does do a few things like check if it could use a smaller (ergo faster) database if it can when applicable, which is something the original did not do you would have had to do it yourself in the PHP side. however i imagine the numbers would be near equal to the original extension.
of course if it turns out ten times faster that would be pretty sweet, lol.