views:

71

answers:

2

Hi good people. I'm working in a web app framework, and part of it consists of a number of services, all implemented as singletons. They all extend a Service class, where the singleton behaviour is implemented, looking something like this:

class Service {
    protected static $instance;

    public function Service() {
        if (isset(self::$instance)) {
            throw new Exception('Please use Service::getInstance.');
        }
    }

    public static function &getInstance() {
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

Now, if I have a class called FileService implemented like this:

class FileService extends Service {
    // Lots of neat stuff in here
}

... calling FileService::getInstance() will not yield a FileService instance, like I want it to, but a Service instance. I assume the problem here is the "self" keyword used in the Service constructor.

Is there some other way to achieve what I want here? The singleton code is only a few lines, but I'd still like to avoid any code redundance whenever I can.

+2  A: 

abstract class Singleton { protected function __construct() { }

    final public static function getInstance()
    {
        static $instances = array();

        $calledClass = get_called_class();

        if (!isset($instances[$calledClass]))
        {
            $instances[$calledClass] = new $calledClass();
        }

        return $instances[$calledClass];
    }

    final private function __clone()
    {
    }
}

class FileService extends Singleton
{
    // Lots of neat stuff in here
}

$fs = FileService::getInstance();

If you use PHP < 5.3, add this too:

// get_called_class() is only in PHP >= 5.3.
if (!function_exists('get_called_class'))
{
    function get_called_class()
    {
        $bt = debug_backtrace();
        $l = 0;
        do
        {
            $l++;
            $lines = file($bt[$l]['file']);
            $callerLine = $lines[$bt[$l]['line']-1];
            preg_match('/([a-zA-Z0-9\_]+)::'.$bt[$l]['function'].'/', $callerLine, $matches);
        } while ($matches[1] === 'parent' && $matches[1]);

        return $matches[1];
    }
}
Coronatus
FYI: This code uses [`get_called_class`](http://us2.php.net/manual/en/function.get-called-class.php), added in PHP 5.3. Doing this in earlier versions is a tad bit trickier.
Charles
Thanks Charles, updated the answer to fix that.
Coronatus
Holy yikes, that's scary. Imagine calling `getInstance` a dozen times, that's a dozen opens and a dozen reads of the class file.
Charles
That's why people should upgrade to the latest and greatest ^^
Coronatus
Thanks! I was actually looking at this function right after I posted the question, but I wasn't sure how to use it to solve the problem. Now I'll just have to wait until the web-hotel guys upgrade to PHP5.3 :)
Johan Fredrik Varen
@Johan you might want to consider ditching the Singletons altogether. They introduce hard coupling to your application and are difficult to test. You can solve the may-have-only-one-instance issue with a Dependency Injection Framework or a Registry.
Gordon
A: 

Had I paid more attention in 5.3 class, I would have known how to solve this myself. Using the new late static binding feature of PHP 5.3, I believe Coronatus' proposition can be simplified into this:

class Singleton {
    protected static $instance;

    protected function __construct() { }

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

        return $instances[$calledClass];
    }

    final private function __clone() { }
}

I tried it out, and it works like a charm. Pre 5.3 is still a whole different story, though.

Johan Fredrik Varen