Singleton Pattern and the T_PAAMAYIM_NEKUDOTAYIM Error

Whilst developing some code for a price comparison website, I came across a PHP error I hadn't seen before.

[Sat Apr 17 09:08:25 2010] [error] [client 127.0.0.1] PHP Parse error:  syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in /srv/www/htdocs/compare/sites/all/modules/compare/includes/merchants/merchantMaster.inc on line 224, referer: http://localhost/compare/

What the dickens is T_PAAMAYIM_NEKUDOTAYIM? It turns out this means 'missing double dots' in Hebrew, and is in reference to the '::' scope identifier in objects.

I am creating a bunch of objects, one per merchant I am connecting to, each only need to be instantiated once. So for instance, I need to go to the Amazon website to get their best price for a product, then say go to Play.com, then LoveFilm etc. I felt the best way would be to have a library of classes, one per merchant. Each merchant has a slightly different way of connectivity, be it REST or SOAP.

A familiar pattern for PHP programmers is the Singleton approach. So my merchant object would look something like this:

<?php
class merchantAmazon {

    private static
$oinstance = NULL;

    private function
__construct() {
    }
   
    private function
__clone() {
    }

    public
getBestPrice($productid) {
       
// connect to remote database here using REST or SOAP or whatever the merchant demands
       
return $price;
    }

   
// Singleton. Only create an instance when necessary
   
public static function getInstance() {
   
       
$classname = __CLASS__;
        if (!
self::$oinstance)
           
self::$oinstance = new $classname();

        return
self::$oinstance;
    }
}
?>

Then to instantiate I would have an array of my merchants and attempt to call the getBestPrice option. This throws the T_PAAMAYIM_NEKUDOTAYIM error!

<?php
    $merchants
= array("Amazon", "Lovefilm", "Play",);
   
$prices = array();

    foreach(
$merchants as $merchant) {
       
$classname = 'merchant' . $merchant;
       
$prices[$merchant] = $classname::getInstance()->getBestPrice($productid);
    }
?>

The reason is the class name cannot be a variable - a serious shortcoming in the PHP language - which has been rectified in version 5.3. All well and good, but I cannot use 5.3 because it is not compatible with the Drupal 6 framework I am using.

Thankfully, there is a workaround - the call_user_func function. Rewrite the above code using the call_user_func. This works without the need for PHP 5.3

<?php
    $merchants
= array("Amazon", "Lovefilm", "Play",);
   
$prices = array();

    foreach(
$merchants as $merchant) {
       
$classname = 'merchant' . $merchant;
       
$obj = call_user_func(array($classname, getInstance));
       
$prices[$merchant] = $obj->getBestPrice($productid);
    }

>
?>

Works, and no error message!