tags:

views:

123

answers:

4

Hi all.

Straight to the point: I've got two singleton classes, both inheriting their singleton nature from a super-class. I initialize some properties on the first singleton, and then have the second singleton retrieve the instance of the first one. That instance, however, does not seem to be the one I initialized in the first place. Some example code might help to explain this:

First, the super-class, providing singleton nature (requires PHP 5.3 or greater):

class Singleton {

    protected static $instance;

    protected function __construct() { }

    final private function __clone() { }

    public static function getInstance() {
        if (!(static::$instance instanceof static)) {
            static::$instance = new static();
        }
        return static::$instance;
    }

}

Then we've got the the first singleton carrying a value:

require_once('Singleton.php');

class SingletonA extends Singleton {

    protected $value;

    public function SingletonA() {
        $this->value = false;
    }

    public function getValue() {
        return $this->value;
    }

    public function setValue($value) {
        $this->value = $value;
    }

}

Then the second singleton that references the first singleton:

require_once('Singleton.php');
require_once('SingletonA.php');

class SingletonB extends Singleton {

    public function getValue() {
        return SingletonA::getInstance()->getValue();
    }

}

Now for the test that shows how this fails:

require_once('SingletonA.php');
require_once('SingletonB.php');

SingletonA::getInstance()->setValue(true);

echo (SingletonA::getInstance()->getValue()) ? "true\n" : "false\n";
echo (SingletonB::getInstance()->getValue()) ? "true\n" : "false\n";

The test yields the following output:

true
false

Clearly, the SingletonA instance that the test code references is not the same instance that the SingletonB instance references. In short, SingletonA is not as single as I need it to be. How is this possible? And what magic can I wield to remedy this behaviour, giving me a true singleton?

A: 

I'm pretty sure it's because you are using static methods, which are not instanced.

Stephen
A: 

SingletonA and SingletonB are different classes. Although they inherit from the same class, they are separate classes and so they have different static instances.

If you change your code to get 2 instances of SingletonA or 2 instances of SingletonB, you will see the behavior you expect. But because they are different classes, they are not the same singleton.

Alan Geleynse
+5  A: 

Try using isset rather than instanceof:

class Singleton {
    protected static $instances;

    protected function __construct() { }

    final private function __clone() { }

    public static function getInstance() {
        $class = get_called_class();

        if (!isset(self::$instances[$class])) {
            self::$instances[$class] = new $class;
        }
        return self::$instances[$class];
    }
}
lonesomeday
Thanks lonesomeday, but that gives me the same instance, which is not what I want.
Johan Fredrik Varen
@Johan You're right, it does. Sorry about that. I have rewritten your class using an array of instances in `Singleton::$instances`, which does work.
lonesomeday
@lonesomeday Thanks a million, man! This version will even run on PHP versions < 5.3, as far as I can see. But I still don't understand why it didn't work.
Johan Fredrik Varen
It won't work pre 5.3, because `get_called_class` was introduced along with `static` in 5.3. And I'm not sure exactly what the problem was either!
lonesomeday
A: 

Let's talk OO. :)

SingletonA and SingletonB are of type Singleton

thus it can be said:

SingletonA is Singleton

and

SingletonB is Singleton

i.e. they're both Singleton

The expected meaning of Singleton means there can be only one. Many people from an OO background using your code will be confused.

Usually, implementation of Singleton would be on a per class basis because most OO languages will not be bent to allow the intent of what you are proposing.

That PHP might do (via get_called_class() magic) doesn't mean it should.

I can absolutely accept that from a utilitarian point of view, the accepted answer looks good. Given the niftyness of the accepted answer, I'd propose a name change that doesn't conflict with "standard" Singleton implementation. From a strict OO point of view, one could never inherit from a Singleton, so it really needs a different name.

spender
@spender Thanks for taking the time to respond in such a generous manner. I won't claim much expertise when it comes to the theory behind the singleton pattern, but while I agree SingletonA and SingletonB can both be said to be instances of Singleton, they're also instances of distinct types. I haven't been able to find any definition addressing the inheritance aspect of the pattern, but the way I see it, is that if a jungle can hold only one tiger and one lion, they can both co-exist even if they're both felines. Again, I claim no greater knowledge of this than that of my own ravings.
Johan Fredrik Varen
@spender I know what you mean here, and I wouldn't design something with multiple singletons anyway (I'd use one at most, and store all the other instances in that one). I agree that it would be better (a) to rename the base class as `BaseSingleton` or something similar.
lonesomeday