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!