Extending singletons in PHP

13,001

Solution 1

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

Solution 2

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 static::$instance;
    }

    final private function __clone() { }
}

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

Solution 3

This is fixed Johan's answer. PHP 5.3+

abstract class Singleton
{
    protected function __construct() {}
    final protected function __clone() {}

    final public static function getInstance()
    {
        static $instance = null;

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

        return $instance;
    }
}

Solution 4

I have found a good solution.

the following is my code

abstract class Singleton
{
    protected static $instance; // must be protected static property ,since we must use static::$instance, private property will be error

    private function __construct(){} //must be private !!! [very important],otherwise we can create new father instance in it's Child class 

    final protected function __clone(){} #restrict clone

    public static function getInstance()
    {
        #must use static::$instance ,can not use self::$instance,self::$instance will always be Father's static property 
        if (! static::$instance instanceof static) {
            static::$instance = new static();
        }
        return static::$instance;
    }
}

class A extends Singleton
{
   protected static $instance; #must redefined property
}

class B extends A
{
    protected static $instance;
}

$a = A::getInstance();
$b = B::getInstance();
$c = B::getInstance();
$d = A::getInstance();
$e = A::getInstance();
echo "-------";

var_dump($a,$b,$c,$d,$e);

#object(A)#1 (0) { }
#object(B)#2 (0) { } 
#object(B)#2 (0) { } 
#object(A)#1 (0) { } 
#object(A)#1 (0) { }

You can refer http://php.net/manual/en/language.oop5.late-static-bindings.php for more info

Share:
13,001
Johan Fredrik Varen
Author by

Johan Fredrik Varen

PHP backend developer at Muuh. Loves Jesus, woodworking, chainsaws, drums, guitars and longs for getting back into SCUBA diving.

Updated on July 01, 2022

Comments

  • Johan Fredrik Varen
    Johan Fredrik Varen about 2 years

    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.