phpinheritancesingletonanti-patterns

Extending singletons in PHP


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.


Solution

  • Code:

    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];
        }
    }