3/25/2010

Building a Fair and Convincing Banner Rotator

I have seen several tutorials how to write a banner rotator online. They are technically correct but looks like the programmers haven't really put their rotators in real environment. They assume that selecting a random banner from the list (or database) will do the work and show the banners equally. Good idea and correct in general.

But as I have run such a rotator in a live site where more than 7-8 banners were rotating, I know this isn't a good enough solution. Why so? Although computer random algos or the SQL RAND() ensure all banners will be shown almost the same number of times in the long run, during 10-20 page refreshes you can keep getting the same 3-4 banners and other ones may not appear at all. This is a problem because the clients who purcased banners complain "Hey, I refreshed more than 10 times and my banner isn't showing even once!!!". Go explain them about random computer algos and that someone might be seeing their banner at the same in some other computer at the other side of the globe.

To avoid such problem, we need not only fair, but also convincing banner rotator - such that will show you the banners equal times when you are refreshing the page.

Let's see what we need:

1. A list or DB table of banners
2. Fair selection
3. Counting the views and clicks

That's in short. We will write two PHP scripts - one will select and output the banner (we'll assume you'll put it in an iframe or will add some other useful stuff to it so you can have a full page with content); and one will redirect the visitors to the targed URL and count the clicks. Let's start:

1. A list of banners

Because we need to save the number of clicks to our banners we'll use a MySQL DB table:

Table banners:
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
image VARCHAR(255) NOT NULL,
url VARCHAR(255) NOT NULL,
clicks INT UNSIGNED NOT NULL;

In the real life you will probably have also a field for customer name, email etc. but let's not worry about this now.

2. Fair selection
This is the most interesting part. Let's call our script rotator.php. It will need not only to select a supposingly random banner, but to ensure that the rotation will be equal even for every single user who is seeing the page. Here's how to do this: we will store the IDs of the banners shown in a session. When selecting, we will make sure we get a random banner from those which have not been shown yet. When all the banners have been shown once in the current session, we'll empty the data and start over. Let's talk code:

rotator.php

// in this array we'll store the shown banners
if(!isset($_SESSION['banner_ids'])) $_SESSION['banner_ids']=array(0);

// let's built the conditional SQL now
$id_sql=implode(",",$_SESSION['banner_ids']);

// now select random banner instead of these that were shown
$q="SELECT * FROM banners WHERE id NOT IN ($id_sql) ORDER BY RAND() LIMIT 1";
$banner=mysql_fetch_array(mysql_query($q));

// enter this banner ID in the already seen ones
$_SESSION['banner_ids'][]=$banner['id'];

// now we need to check whether all the banners were shown once
$q="SELECT COUNT(id) FROM banners";
$num_banners=mysql_fetch_row(mysql_query($q));

// we use "<" because our session array always contains one more ID - the zero which is
// there to prevent mysql error
if($num_banners[0] < sizeof($_SESSION['banner_ids']))
{
unset($_SESSION['banner_ids']);
}

// that's it! now just display the banner:
echo "<a href='go.php?id=$banner[id]'><img src='$banner[image]'></a>";


Counting the views and clicks
Now this is pretty straightforward:

go.php

// prevent hacking
if(!is_numeric($_GET['id'])) exit;

// select banner
$q="SELECT * FROM banners WHERE id='$_GET[id]'";
$banner=mysql_fetch_array(mysql_query($q));

// update clicks
$q="UPDATE banners SET clicks=clicks+1 WHERE id='$banner[id]'";
mysql_query($q);

// redirect
header("Location: $banner[url]");


That's all!