Text

Jul 3, 2010
@ 4:05 pm
Permalink

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