Web Development

Effective Geotargeting with PHP

Introduction

In this tutorial, we’ll take a look at the technique of geotargeting,
or serving content to users based on their physical location. The
technology is invaluable; with simple techniques, you can target
advertising to specific users, collect more accurate usage statistics,
serve content in different languages for different regions and provide
local information like weather reports to your visitors. I’ll briefly
cover what you need to know about geoip, and then move on to some code
samples of how to achieve it with the Maxmind database, using both the
standard PHP library and the PEAR version.

IP Targeting: It’s tricky business!

For geo location, the IP address of your users is probably the most
useful piee of information. The IP address of a user can be matched
against a database of current IP allocations globally to work out where
the user is based; the trouble is maintaining an up-to-date database of
the current IP allocations. Economies of scale kick in at a point, and
a company called MaxMind manage to offer some pretty accurate
geolocation technology entirely free of charge. If you’re interested in
the technical details, they have a detailed explanation of the IP location process on their website. Their database is so popular that there’s even a PEAR package
to suit, although MaxMind provide their own pure PHP library and PECL
extension — we’ll cover both pure PHP versions in this tutorial.

Getting started

The database is the key to all our GeoIP work — the better the
database, the more accurate the country check. The free database from
MaxMind offers “Over 98%” accuracy, which is pretty good, but a 2%
failure rate can be a little high for businesses. We’ll stick to the
free version for the moment; you can grab a copy here — you’ll need the “latest GeoLite Country Binary Format” version. You’ll also need to get their PHP library here. Finally, download and install the PEAR package, Net_GeoIP — you can do this via command line or grab the package file here.
Access to a PHP webserver is assumed. Of course, there’s no need to try
out these samples; the code is pretty light-weight so you can follow
along without testing it out.

Incidentally, if you want to write your own lookup system, a CSV
version of the actual IP database is available that is very easy to
work with when imported into a SQL database. Bear in mind, however,
that the database is very large, and another lookup to a database table
for every single user can cause significant server load.

Let’s write some code!

Once you’ve got the library and the database — throw them all into
the same folder for simplicity — it’s actually pretty easy. Load up
your favourite text editor and type this in:

  <?php  require_once 'geoip.inc';  $gi = geoip_open( 'GeoIP.dat', GEOIP_STANDARD );  $country = geoip_country_code_by_addr( $gi, $_SERVER['REMOTE_ADDR'] );  echo $country;  ?>

This presumes you’ve put both the geoip.inc library and geoip.dat
database in the same folder. $_SERVER['REMOTE_ADDR'] should represent
your IP address. If you’re behind a proxy or firewall this could be
somewhat inaccurate and possibly even a little misleading, but
generally this should work fine.

You won’t get much luck running this on your local machine, as your
IP would be a wonderfully non-descriptive 127.0.0.1; you’ll have to
upload it to a remote server to see the results. You should see a very
short country code; a couple of letters, nothing else. For example,
here’s my result:

AU

That may not look like much, but it’s not just any country code:
it’s your country code. In four lines of code we’ve worked out a
standard country code corresponding to your location — and imagine the
possibilities with that! Of course, there’s much more we can do. From
here, experiment with serving special content to particular countries
and having friends abroad try to visit the page.

A touch of PEAR

We’re all for code re-use here, so let’s take a look at the PEAR
version. Just a brief intro: PEAR is a repository of PHP libraries all
built in approximately the same somewhat predictable manner. They are
designed to be highly reusable, making them fantastic for completing
projects in short timeframes, and help you avoid reinventing the wheel.
But PEAR isn’t just about libraries; it’s a fully-fledged package
distribution system that probably came installed with your version of
PHP, as well as a community creating and supporting all this great
work. See pear.php.net for more info.

The PEAR version works in much the same way, only has improved PHP 5
style error handling and is a little more clear. Here’s how to do it
with the PEAR library:

  <?php  require_once "Net/GeoIP.php";  $geoip = Net_GeoIP::getInstance("/path/to/geoip.dat");  try {      echo $geoip->lookupCountryCode($_SERVER['REMOTE_ADDR']);  } catch (Exception $e) {      // Handle exception  } 

Notice here we can natively catch any errors that occur; in the
older version, we had to test if no value was returned. The PEAR
version is not necessarily more reliable at conducting lookups — it’s
using the same database — but it will be a little easier to work with
if you’ve used PEAR packages before, and it also makes use of some
useful PHP 5 features. I recommend using the PEAR version, but it’s up
to you and for basic use won’t make much of a difference. For high
traffic sites with basic geolocation needs, you might find that the
original version offers better performance as it doesn’t come with the
overhead of an object.

Performance considerations

Let us take a look at our original code snippet:

  <?php  require_once 'geoip.inc';  $gi = geoip_open( 'GeoIP.dat', GEOIP_STANDARD );  $country = geoip_country_code_by_addr( $gi, $_SERVER['REMOTE_ADDR'] );  echo $country;  ?>

All very well; nice and simple. But is it really? What’s going on behind the scenes with geoip_open() and geoip_country_code_by_addr()?
If we do a quick benchmark, on a standard shared web host this script
takes on average 0.05 seconds to execute. Now, that might not seem like
a lot, but consider that on average an entire script to generate a
database-driven page takes 0.01 seconds on the same server. With just
20 hits per second, your new GeoIP script could slow your server to a
crawl, not to mention the processing required by the rest of your
website. Given this, you definitely want to be caching your lookup,
possibly in session data.

Visuals

Of course, this country code isn’t of much use on its own anyway —
so how about we make it a little more useful to your users? Show them a
small version of their country’s flag to invoke the patriot inside
anyone! From Mark James, of Silk icon set fame, comes a fantastic library of 247 flags for just about every country out there.
What’s more, they’re small, totally free, look good on any web page and
follow the same naming conventions as your PHP routine. Just drop them
in place, lowercase your country codes and show the appropriate flag to
easily impress any user.

A complete geotargeting system

Given these performance considerations and the visuals possible with
the flag icons, let’s create a simple geotargeting system that you can
drop in to your website. First, we initialise:

  <?php  session_start();

Note that including our geoip.inc library is in itself overhead we
would like to avoid where performance is an issue. As a result, we deal
with it later instead.

We need our script to save the user’s country in the session and
show a nice little PNG of the country’s flag. So, we first test if the
session data isn’t there, and set it accordingly using our script from
above:

  if  (!isset($_SESSION['geocountry'])) {      require_once 'geoip.inc';      $gi = geoip_open( 'GeoIP.dat', GEOIP_STANDARD );      $country = geoip_country_code_by_addr(  $gi,                                               $_SERVER['REMOTE_ADDR'] );      $_SESSION['geocountry'] = $country;  }

On subsequent page loads, the ‘geocountry’ session variable would
already have been set by this snippet; it essentially serves as a
user-based cache, and we leave the tricky session handling to PHP
(although you may want to use your own session library as part of a
framework). Next, we display the PNG flag:

  $country  = strtolower($_SESSION['geocountry']);  echo '<img src="flags/png/'.$country.'.png" />';

Now, load the script up in your web browser. With any luck you should see something like the following:

It may seem rather small, but it’s actually the perfect size for
fitting into a content-rich web page, and works quite nicely inside
navigation bars and menu sections of any size. It’s also very
recognisable to the user as their flag, and finally, after all the
pretty visuals are dealt with, we now have the user’s country available
for use in our session’s ‘geocountry’ variable.

Tips and tricks

Typically automatic language selection was very tricky, as browsers
often weren’t setup properly to tell the server what languages the end
user expected. The remedy was to ask the user directly, but a long list
of languages was very daunting for the end-user, and leaving some
minority languages out was potentially insulting. With geolocation, you
could combine the user’s country with a database of locales based on
country, then default to one but offer a clear choice of two or three
(as appropriate) for the user to choose from; problem solved.

Content delivery networks also use geotargeting a lot. The idea of a
content delivery network is that servers located physically closer to
the user often incurred lower data transfer costs and were somewhat
quicker, an important factor in winning users. With geotargeting, for
example, all images from a site can be fed off a local server.
Alternatively, you could redirect the user to the local version of your
website altogether, possibly even on an entirely new domain. Say your
domain was example.com, you might want to redirect me to
example.com.au, where your local site is not only quicker to load, but
has better targeted content and more relevant ads as well.

Going further

We aren’t limited by country codes, of course — if you take a look
through the documentation for the PEAR package, you’ll find methods for
country name, region and even organization / ISP! Experiment with
multiple versions of your website content in a format that you can
easily work with; if you have a content management system, you may be
able to build a feature to create pages for users from a particular
country. If you’re looking for slightly more accurate lookups, MaxMind
also offers city databases that complement the country database we
worked with in this tutorial, and they have paid versions of both that
offer somewhat better accuracy. Be creative and have fun guiding your
users to a new world, where the information knows just a little more
about them.

About the author

Written by .

If you found this post useful you may also want to check these out:

  1. Effective Field Visibility in Java Programs
  2. Before you code…
  3. Publishing Newsletters Using PHP & MySQL
  4. Replacing Text in a MySQL Database Using PHP
  5. Adding records to a MySQL database using PHP
  6. How Many Users Online?
  • derekbennett

    I have searched far and wide for a geo-targeting method that suited my needs. Basically I needed to do a handful of things:

    1) Forward to another URL based on city/region/country/etc
    2) Get the latitude and longitude of the detected city
    3) Be extremely fast
    4) Determine the closest major city

    I cooked up my own solution to handle the first 3. I used MaxMind’s free GeoIP database (which is fairly accurate). This worked fine, but I didnt like that it was displaying whatever tiny town was detected. If a user saw the small town, they would instantly know they’re being tricked. I wanted to say “Ok find the closest city with a population of over 2000000 people and display it”. After a while I finally found Lambda GeoIP which I liked for a few reasons:

    -Free updates for life (to both the script and the database
    -Lots of examples and functions to make using it super easy
    -The nearest major city feature

    But there were also one thing I didnt like about it

    -No Javascript built in, so if you needed to use it in javascript you’d have to code something up to pull from the script first.

    Overall, though, I like it and still use it. A lot of people make silly mistakes when doing geo-targeting, you just gotta be clever. If you use geo-targeting correctly its a huge benefit.