Extending Singleton
I originally wrote this post for tsensus.com, but I’m migrating to tumblr, so I’ve reposted it here:
Tired of writing getInstance() methods and overloading the __construct() and __clone() methods for all of my Singletons, today I developed a Singleton that can be extended. The result is as follows:
class Singleton {
protected static $_instance = array();
private function __construct() {}
private function __clone() {}
public static function getInstance() {
$class = get_called_class();
if (null === self::$_instance[$class]) {
self::$_instance[$class] = new $class();
}
return self::$_instance[$class];
}
}
The $_instance variable is an array because, as a static variable, it wouldn’t allow for more than one class to extend it. For instance, suppose two classes, Widget and Gadget, extend Singleton. When Widget is first created with Widget::getInstance(), if it weren’t an array, $_instance would be set to a Widget object. Since this variable is static, calling Gadget::getInstance() would just return the previously instantiated Widget object. So the solution to this problem was to make $_instance an associative array with the keys being the name of the calling class.
I use the get_called_class() function to retrieve the name of the class calling getInstance(). So if you call Widget::getInstance(), the result of get_called_class() would be “Widget”. If you used the __CLASS__ magic constant, you would get “Singleton” which is not what we want. The problem with this is that get_called_class() was only implemented in PHP v5.3, so this won’t work with prior versions of PHP (which is what I’m using). Luckily I found this post which creates a suitable get_called_class() function if it doesn’t exist. The code is duplicated below, thanks to Septuro:
if (!function_exists('get_called_class')) {
class class_tools {
static $i = 0;
static $fl = null;
static function get_called_class() {
$bt = debug_backtrace();
if (self::$fl == $bt[2]['file'].$bt[2]['line']) {
self::$i++;
} else {
self::$i = 0;
self::$fl = $bt[2]['file'].$bt[2]['line'];
}
$lines = file($bt[2]['file']);
preg_match_all('/([a-zA-Z0-9\_]+)::'.$bt[2]['function'].'/', $lines[$bt[2]['line']-1], $matches);
return $matches[1][self::$i];
}
}
function get_called_class() {
return class_tools::get_called_class();
}
}
It should be noted that if the child class has a constructor, it should be declared protected so the Singleton class can instantiate it upon the first call to getInstance().