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/',
  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.

Posted on January 24, 2007 in PHP

Comments

  1. 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. 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 (Author comment)

    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. 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. In terms of your last loop - it looks better with ‘foreach’ for me..

    Declaring a new $i value is not necessary.

  7. foreach($randItems as $rand_Items)
    {
        $item = $items[$rand_Items];
    }
    
  8. 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 (Author comment)

    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. 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. 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 (Author comment)

    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 (Author comment)

    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. <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. 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. 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. 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. 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. 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. One more improvement:

    $numberOfItems = count($items);

  24. 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. (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. 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. <?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. @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. @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. Thanks, Roger! This is great. I’m just learning PHP and this will be great to practice with.

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

  34. 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. 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 (Author comment)

    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. 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. 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. It works perfect. Nice piece of code :-)

  41. 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. 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 (Author comment)

    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?

Comments are disabled for this post (read why), but if you have spotted an error or have additional info that you think should be in this post, feel free to contact me.