selfcontained[web development]

PHP and an abstract Singleton

Wednesday April 20, 2011
By Brad Harris

PHP is quirky. I've heard it called other things, but I'll leave it at that. I had a great learning experience while writing an abstract singleton class. Static binding in PHP behaves uniquely, and is often a hurdle when you try and develop an kind of an API using inheritance and static properties. If you aren't familiar with static binding in PHP, read the doc on late static binding and it will give you a good overview. This issue came up while creating an abstract singleton class.

Let me explain by example. Here's my first pass at a simple abstract singleton class that I could theoretically extend from other classes, and just pick up "singleton" behavior. Disclaimer!!! Don't use this class, it doesn't work!

abstract class Singleton {

    private static $instance = null;

    final public static function getInstance() {

        if(static::$instance === null) {
            static::$instance = new static();
        }
        return static::$instance;
    }

    protected function __construct() { }

}

This worked, initially, until I created two classes that extended Singleton and found the second one I called behaved remarkably like the first one.

class FirstSingleton extends Singleton {
...
}
class SecondSingleton extends Singleton {
...
}

Any calls to SecondSingleton::getInstace() were really just returning the FirstSingleton instance. Since the private static $instance property is delcared on the abstract Singleton class, there was really just one $instance being stored across calls to getInstance() from various subclasses. PHP doesn't bind the $instance property to the called class, it is bound to the class where it is declared, Singleton in this case. You could declare the $instance property on all classes that extend Singleton, but that is defeating the point of the abstract class. Here's round two, which does work, but I have mixed feelings about:

abstract class Singleton {

    private static $instances;

    final public static function getInstance() {
        $className = get_called_class();

        if(isset(self::$instances[$className]) == false) {
            self::$instances[$className] = new static();
        }
        return self::$instances[$className];
    }

    protected function  __construct() { }

}

The change here is that instead of storing a single $instance property on the abstract Singleton class, we're storing an array of instances, indexed by the class name of the called class. It behaves as expected, but the code isn't as clear or clean as I would have hoped it would be.

blog comments powered byDisqus