Simple PHP randomizer

On a regular basis I get questions about the randomized books and music albums that are displayed on this site. A common question is if I use a plugin for that, and the answer is no, I use a custom PHP script.

The script is quite simple, but since I do get questions about it I think a quick explanation of how it works is in place, if only to serve as something to refer future questions to.

The script consists of three parts. The first part is an array of arrays, with each sub array containing the href and title attributes of the link plus the src and alt attributes of the image for one book (only two array entries are displayed here):

  1. echo '<ul>' . "\n";
  2. $items = array(
  3. 1 => array(
  4. 'href' => 'http://www.amazon.com/exec/obidos/ASIN/1590595815/456bereastree-20',
  5. 'title' => 'Buy Blog Design Solutions from Amazon.com',
  6. 'src' => '/i/r/1590595815.01.jpg',
  7. 'alt' => 'Buy Blog Design Solutions from Amazon.com'
  8. ),
  9. 2 => array(
  10. 'href' => 'http://www.sitepoint.com/launch/18a7ecc/3/54',
  11. 'title' => 'Buy Build Your Own Web Site the Right Way from SitePoint.com',
  12. 'src' => '/i/r/build-your-own-website-the-right-way.jpg',
  13. 'alt' => 'Buy Build Your Own Web Site the Right Way from SitePoint.com'
  14. ),
  15. /* Add as many entries as needed. */
  16. );

In this case the title and alt attributes contain the same text. The image is linked, so its alt attribute should describe the link target for screen reader users and users with images disabled. The title attribute provides the same information to sighted mouse users with images enabled. I chose to do it this way in case I should want to use different text for those two attributes.

The second part randomly picks three items from the array:

  1. $numberOfItems = 3;
  2. $randItems = array_rand($items, $numberOfItems);

When the array_rand function is called with a second argument (3 in this case), it returns an array consisting of that number of entries, randomly picked from the array passed in the first argument.

Finally, the third part is a loop that outputs a list item containing a link and an image for each entry in $randItems:

  1. for ($i = 0; $i < $numberOfItems; $i++) {
  2. $item = $arrItems[$randItems[$i]];
  3. echo "\t" . '<li class="r' . ($i + 1) . '"><a href="' . $item['href'] . '" title="' . $item['title'] . '"><img src="' . $item['src'] . '" alt="' . $item['alt'] . '"></a></li>' . "\n";
  4. }
  5. echo '</ul>' . "\n";

To keep the generated HTML tidy, each line begins with a tab character (\t) and ends with a newline character (\n). They aren’t necessary for the HTML to work, so you may or may not want to keep it that way. The class attributes for the list items are there to let me float the first two in opposite directions with the following CSS snippet:

  1. .r1 img {float:left}
  2. .r2 img {float:right}

And that’s it. php-randomizer.phps is a generic example file that contains all parts of the script. The script is free to use, no strings attached.

At first I was a little worried that using this script for lots of things would use a lot of processing power. That does not seem to be the case, however. I use this script (or variations of it) for four different sets of content on every page load on this site (“Recommended books”, “Recommended music”, “Best of 456 Berea Street”, and “Random places to go”) and have not noticed any performance problems despite getting plenty of traffic and being on a shared server.

As I am not a PHP expert, feel free to suggest improvements.

  • January 24, 2007
  • Comments closed
  • Posted in

Comments

1. January 24, 2007 by Douglas Clifton

Unless your list is fairly small, I would tend to store records like this in a (My)SQL database. I find it is much easier to add, edit, and remove such records without having to touch the code. But hey, that's just me.

2. January 24, 2007 by mgroves

I agree with Douglas. Pulling from a SQL table randomly is easy as dirt (SELECT FROM tbl ORDER BY RAND() LIMIT 5) or (SELECT TOP 5 FROM tbl ORDER BY RAND()), and with SQL you have the added bonus of being able to construct a back-end to make add/edit/delete easier on yourself (this benefit grows for multi-author blogs).

3. January 24, 2007 by Roger Johansson

Yeah I could use MySQL, but that does add an extra level of complexity. This is for small lists and when you don't want to (or don't know how to) bother with a database.

4. January 24, 2007 by Waldek Mastykarz

You could try improving the script by replacing " (double quote) with ' (single quote). It takes PHP parser more time to render text between " (double quote) because it allows to put there variables as well. Further you could try to replace for with foreach: I think it would make that part a bit more readable. On the contrary you won't be able to use your CSS anymore: a reason to find a creative solution, maybe? :) A database based solution seems just to heavy to me...

5. January 24, 2007 by Jeff Khonsary

Roger, thanks for the write-up.

As per Waldek's comment: Swapping your double quotes with single quotes around your strings would mean you could then drop the "\" escape characters too. I have heard for is speedier then foreach -- not sure if that is true. I tend to use for more often as it saves having to create new named variable (in place of "$i").

6. January 24, 2007 by Rafal Zrobecki

In terms of your last loop - it looks better with 'foreach' for me..

Declaring a new $i value is not necessary.

7. January 24, 2007 by Rafal Zrobecki
foreach($randItems as $rand_Items)
{
    $item = $items[$rand_Items];
}
8. January 24, 2007 by Henrik N

The redundancy hurtsss usss. Assuming you'll always want the same format for all alt attributes and for all title attributes, with only variations in the book title and vendor, I'd suggest storing the title and the vendor in the array instead, and putting "Buy … from …" in the loop.

9. January 24, 2007 by Roger Johansson

Waldek:

You could try improving the script by replacing " (double quote) with ' (single quote).

Only in the echo statements or in the array as well?

Jeff:

Swapping your double quotes with single quotes around your strings would mean you could then drop the "\" escape characters too.

Except for the tab and newline characters, right?

Rafal: Thanks, foreach does look tidier, but as Waldek mentions I won't be able to add the enumerated class names if I do that, unless I include a variable in the foreach loop. And that partly defeats the purpose of making it tidier.

Henrik: Absolutely, but I wanted to make this a bit more flexible.

10. January 24, 2007 by Sebastian Redl

Using ' instead of " is not a good idea in this case: \t and \n are not interpreted in single-quoted strings. Personally, I would drop out of PHP mode entirely for most of the content and only insert the dynamic bits:

for(...) { ?> <li class="r"> ... <? }

But that's just personal preference. Another thing I might do instead is using the variables inside the strings, and also single-quotes, which is entirely valid, and I haven't seen a browser that has problems with them lately. (NS4 might have had. Not sure.)

echo "\t<a href='{$item['href']}' title='{$item['title']}' ...\n";

If you want the foreach loop and keep the numbered index, you can do it like this: foreach($randArr as $i => $idx) { }

But it is indeed not that much more readable.

If you ever feel like doing it in SQL, then please, before using ORDER BY RAND(), read this: http://jan.kneschke.de/projects/mysql/order-by-rand/ The ORDER BY RAND() struck me as a bad idea the moment I saw it, and this article confirms it (and provides a nice solution).

11. January 25, 2007 by ErikHK

You know you can save the file as .phps, so it will be syntax highlighted also?

12. January 25, 2007 by Adam Mulligan

Nice little script there :) If you want a little more flexibility, and one less step when adding more items, you could change $numberOfItems to equal count( $items ); And also, as it was suggested, if you replace the double quotes with single, the \n and \t will no longer work.

13. January 25, 2007 by Roger Johansson

Sebastian:

Using ' instead of " is not a good idea in this case: \t and \n are not interpreted in single-quoted strings.

Right. I have updated the script to use single quotes everywhere except for printing \t and \n. Thanks for the tip about foreach. I think I'll stick with for.

ErikHK: I do now ;-). Thanks.

Adam:

you could change $numberOfItems to equal count( $items )

$numberOfItems is the number of items to be randomly picked from the array, not the number of items in the array ;-).

14. January 25, 2007 by Brett Mitchell

Roger,

I /really/ don't like using double quotes and single quotes in the same variable or output statement, so a tip to fellow readers if you're like me: use chr(n). Simple ASCII -- 9 is tab, 10 is new line.

echo chr(9) . '<li class="r">Text</li>' . chr(10);

I haven't tested it, and for small scripts it doesn't make a lick of difference, but over long periods of time and heavy scripts I'm fairly confident it will be much faster than using double quotes too.

If you need to use 5 (say you're coding a sublist of a list inside a div inside another div), I've found it handy to use something like:

$tab5 = chr(9).chr(9).chr(9).chr(9).chr(9);

then your echo statement is nice and tidy:

echo $tab5 . 'content' . chr(10);

I don't doubt for a minute that yourself and most other readers know this already, but if you don't, well, maybe I just helped you clean up your code.

15. January 25, 2007 by Brett Mitchell

Despite my preview and replacing the < and > with & g t ; etc it seems to have displayed incorrectly in my post anyway, and also caused a few warnings on my HTML Tidy... my apologies, Roger, if you could edit that to display properly?

16. January 25, 2007 by Roger Johansson

Brett: Sorry about the comment preview. I'm very close to giving up on it and will probably disable HTML completely in comments soon. I just can't solve the problem :-(. I fixed up your comment.

About your tip, thanks, I didn't think of that actually.

17. January 25, 2007 by Douglas Clifton
<ul>
<?php

// data here -- there is no need to index the outer array

$numberOfItems = 3;
$i = 1;

foreach (array_rand($items, $numberOfItems) as $key) {
    $class = 'r' . $i;
    $href  = $items[$key]['href'];
    $title = $items[$key]['title'];
    $src   = $items[$key]['src'];
    echo <<<LI
 <li class="$class"><a href="$href" title="$title">
  <img src="$src" alt="$alt" /></a></li>

LI;
    $i++;
}

?>
</ul>
18. January 25, 2007 by Douglas Clifton

Or even simplier:

<ul>
<?php

// data here -- there is no need to index your outer array

$attrs = array('href', 'title', 'src', 'alt');
$numberOfItems = 3;
$i = 1;

foreach (array_rand($items, $numberOfItems) as $key) {
    $class = 'r' . $i;
    foreach ($attrs as $attr) $$attr = $items[$key][$attr];
    echo <<<LI
 <li class="$class"><a href="$href" title="$title">
  <img src="$src" alt="$alt" /></a></li>

LI;
    $i++;
}

?>
</ul>

Okay, I'll shut up now. ;-)

19. January 25, 2007 by Mau

Roger, this is awesome!

I am not very well versed in PHP, so this is something to keep as a reference!

Thanks a bunch!

20. January 25, 2007 by Mark

Using the printf() function instead of an echo would also help clean up your output quite a bit. That's my preferred method of displaying a string with quite a bit of dynamic content.

21. January 25, 2007 by Mike Cherim

I like the script, Roger, and it's been nicely tuned. I especially like the .phps file type as ErikHK mentioned. I did not know that. That in itself in a choice little nugget of info. And that wasn't the only one either. This article and its comments are brimming with goodness :-)

22. January 25, 2007 by Mats Lindblad

If you are using local images, or in some cases remote as well, and you want to add image size to your <img /> tag(element?)

list($width, $height, $type, $attr) = getimagesize("img/flag.jpg");

and the $attr variable will contain the pre-formatted height and width attributes.

PHP Manual

23. January 25, 2007 by Dominik Hahn

One more improvement:

$numberOfItems = count($items);

24. January 25, 2007 by Ash Searle

Expanding on Douglas Clifton's example, but using extract, and in-lining the class-attribute:

<ul>
<?php

// data here -- there is no need to index your outer array

$numberOfItems = 3;

foreach (array_rand($items, $numberOfItems) as $i => $key) {
    extract($items[$key]);
    echo <<<LI
<li class="r$i"><a href="$href" title="$title">
    <img src="$src" alt="$alt" /></a></li>
}
?>
</ul>
25. January 25, 2007 by Ash Searle

(The LI; line's gone AWOL in my code... it should be in the same place as in Douglas's. But there's no need for the "$i++;" following it)

26. January 25, 2007 by Simon Hanmer

I have a similar chunk of code that I use for displaying random images:

<div id="random_pics">
<?php
$file_array = array();

$dir = opendir($_SERVER['DOCUMENT_ROOT']."/images/random");
while ($file = readdir($dir) ) {
 if ($file != 'Thumbs.db' and substr($file, 0, 1) != '.' )    array_push($file_array, $file) ;
}

foreach (array_rand($file_array, 4) as $file) {
printf(
    "<img style=\"border: 1px solid black; margin: 0.2em 0\" src=\"/images/random/%s\" alt=\"local views\" width=\"200\" height=\"150\" /><br />\n",
                  $file_array[$file]);
}

?>
</div>

Basically, I set up a directory /images/random and place pre-sized images in there, not so clever as the original script, but it does work quite nicely as a little gallery-type chunk of code.

27. January 25, 2007 by Rafael

If you want to make it even simpler, instead of using array_rand and using $numberOfItems, you could just use shuffle(). Then you wouldn't need to assign a number as the key for each array. You just shuffle it and then pick out the first three since they will be random anyway.

And if you don't want to use MySQL you could pull them out of a text file. Have each item on its own line and separate its title, href, src, etc. with a character of your choice. Then all you have to do is use file() to make an array with each element as a line from the file. Then use explode() to separate the pieces into alt, href, etc.

28. January 25, 2007 by Rafael

<?php

$items = file('items.txt');

shuffle($items);

for ($i = 0; $i < $numberOfItems; $i++) {

list($href, $title, $src, $alt) = explode("|", $items[$i]);

echo "\t<li class=\"r" . ($i + 1) . "\"><a href=\"$href\" title=\"$title\"><img src=\"$src\" alt=\"$alt\"></a></li>\n";

}

?>

29. January 25, 2007 by Douglas Clifton

@Ash

Good point on using extract() over the inner foreach loop--by and large using a PHP function over a loop is more efficient. Now the logic of the script is boiled down to only 14 lines of code. One thing you would have to change is the CSS, as the first class would be "r0" rather than "r1."

30. January 25, 2007 by Douglas Clifton

@Rafael

I agree completely with the idea of seperating the data from the code. However, I would use an include file that builds the array and returns it. That way if you wanted to later store them in a database, or whatever, the logic part of the script wouldn't have to know anything about it.

As far as using shuffle(), that's another interesting approach, although for a large list it probably wouldn't make much sense to shuffle the whole thing if you're just going to select the first N items.

31. January 25, 2007 by Jeremy

you could also store the urls in a text file...that way it's not as difficult as mySQL and more dynamic than embeded urls in the file

:)

32. January 26, 2007 by Jen

Thanks, Roger! This is great. I'm just learning PHP and this will be great to practice with.

33. January 27, 2007 by William

I'm completely new to PHP - any advice on a good intro level tutorial?

34. January 28, 2007 by Alexander Graf

I disagree with those suggesting SQL. A connection to a database is always more 'expensive' than filtering through a relatively small array of, say, 20 books.

Nice, simple and clean solution Roger.

35. January 30, 2007 by Doufer

I'm gettingg the message:

Warning: array_rand() [function.array-rand]: First argument has to be an array in /home/.nefertiti/doufer/doufer.com.br/wp-content/themes/Neat/banner.php on line 42

The code is here: doufer.com.br/wp-content/themes/Neat/banner.php

You can see the message in this page: http://www.doufer.com.br/faf

The code was tested?

Thanks!

36. January 30, 2007 by Roger Johansson

Doufer: Are you calling the array_rand() function properly? Sounds like the argument isn't an array. The code has been tested - I use it for several things here on this site.

37. February 1, 2007 by Rossy

Doufer: You can rename the array as $Items,The example has a little problem with the character .Such as $Items , $arrItems ,You should to rename it and let the name is the same.

38. February 9, 2007 by Lars Gunther

Three discussions here:

  1. Data storage: Array, file or DB?

  2. "As few lines of code as possible"

  3. Speed optimization.

One might add a few more:

a. Code reusability/maintainability

b. Code readability

In every way Roger's script can be improved. I do not think that's the main point. This was a snippet of use to him and perhaps some more. Not a full-fledged MVC framework.

Personally I would probably put my data in a DB, then use a DB-cache to speed things up and have a separate template for the output. I'd also make sure that no variable would end up in the global namespace but be properly encapsulated in a class. But hey, I just like to overdo things!

39. February 10, 2007 by Zwirko

I know I'm being a little thick here, but how do you implement this script? What goes where?

Thanks in advance to anyone who helps this confused noob.

40. February 26, 2007 by Stefan

It works perfect. Nice piece of code :-)

41. March 1, 2007 by Doufer

Thank's Rossy!

It work's now.

42. April 3, 2007 by pawan

good

43. May 19, 2007 by Liam

Is there a way to display only 1 image randomly? When I set the $numberofItems to 1, it gives an error.

44. May 19, 2007 by J.C.

Another method for printing text strings with variable processing is using heredoc notation (rather than jumping in and out of PHP mode, as one person mentioned above.)

45. July 27, 2007 by Graham

Hi Roger. The script looks great. However I cannot get it to work. I cut and paste it into a php page, added the image URL (which are local) title and all the other stuff. And I get...

Its not very clear how you get this to work, I am doing something wrong?

•   Warning: array_rand() [function.array-rand]: First argument has to be an array in...

How do you add this to a webpage? Many thanks

46. July 29, 2007 by Roger Johansson

Graham: It's very hard to tell what is going wrong without seeing the actual source code of your page. Would it be possible for you to provide a little more info?

Sorry, comments are closed for this post.

Information, sponsorship, and externals

About the author

Roger Johansson is a Swedish web professional specialising in web standards, accessibility, and usability. More about me and this site.

Subscribe

Looking for web hosting?

Try DreamHost!

Use the promo code 456BEREASTREET3 to save USD 20 when you sign up!

Latest articles

Validation statistics from Nikita the Spider Comments off
An analysis of the sites crawled by the bulk validation tool Nikita the Spider during March 2008.
Authentic Jobs API and Affiliates program Comments off
The Authentic Jobs job listing service now has a public API and an affiliate program.
What does Acid3 mean to you and me? Comments off
Opera and Apple have announced that their web browsers pass the Acid3 Browser Test, but how will that help web designers and developers?
Designing Web Navigation (Book review) Comments off
Learn the fundamentals of navigation design and design better navigation systems for large and small sites as well as for web based applications.
DOMAssistant bundle for TextMate Comments off
To save keystrokes and speed up development I have created a DOMAssistant bundle for TextMate.
First impressions of Internet Explorer 8 Beta 1 Comments off
My impressions after trying out Internet Explorer 8 Beta 1 for a couple of days.

More articles

Favourites, here and elsewhere

Affiliation

  • NetRelations
  • Kaffesnobben
  • Dagens recept
  • 9rules network member

Support this site

Show your support by buying a book or two from SitePoint or getting me something from my Amazon Wish List.