开发者

Implement a PHP singleton: static class properties or static method variables?

开发者 https://www.devze.com 2023-01-07 15:38 出处:网络
So, I\'ve always implemented a singleton like so: class Singleton { private static $_instance = null; public static function getInstance() {

So, I've always implemented a singleton like so:

class Singleton {
    private static $_instance = null;
    public static function getInstance() {
        if (self::$_instance === null) self::$_instance = new Singleton();
        return self::$_instance;
    }
    private function __construct() { }
}

However, it recently struck me that I could also implement it with member-wise static variables:

class Singleton {
    public static function getInstance() {
        //oops - can't assign expression here!
        static $instance = null; // = new Singleton();
        if ($instance ===开发者_Go百科 null) $instance = new Singleton();
        return $instance;
    }
    private function __construct() { }
}

To me, this is cleaner because it doesn't clutter the class, and I don't have to do any explicit existence check, but because I've never seen this implementation anywhere else, I'm wondering:

Is there anything wrong with using the second implementation over the first?


Go with a class property. There are a few advantages...

class Foo {
    protected static $instance = null;

    public static function instance() {
        if (is_null(self::$instance)) {
            self::$instance = new Foo();
        }
        return self::$instance;
    }
}

First, it's easier to perform automated tests. You can create a mock foo class to "replace" the instance so that other classes which depend on foo will get a copy of the mock instead of the original:

class MockFoo extends Foo {
    public static function initialize() {
        self::$instance = new MockFoo();
    }
    public static function deinitialize() {
        self::$instance = null;
    }
}

Then, in your test cases (assuming phpunit):

protected function setUp() {
    MockFoo::initialize();
}

protected function tearDown() {
    MockFoo::deinitialize();
}

This gets around a common gripe with singletons that they are hard to test.

Second, it makes your code more flexible. If you ever want to "replace" the functionality at run time in that class, all you need to do is subclass it and replace self::$instance.

Third, it allows you to operate on the instance in other static function. This isn't a huge deal for single instance classes (a true singleton) since you can just call self::instance(). But if you have multiple "named" copies (say for database connections or other resources where you want more than one, but don't want to create a new one if they already exist), it becomes dirty because you then need to keep track of the names:

protected static $instances = array();

public static function instance($name) {
    if (!isset(self::$instances[$name])) {
        self::$instances[$name] = new Foo($name);
    }
    return self::$instances[$name];
}

public static function operateOnInstances() {
    foreach (self::$instances as $name => $instance) {
        //Do Something Here
    }
}

One other note, I wouldn't make the constructor private. It will make it impossible to extend or test properly. Instead, make it protected so that you can sub-class if needed and still operate on the parent...


You probably mean it with a slight modification (I got a syntax error otherwise):

<?php
class Singleton {
    public static function getInstance() {
        static $instance;
        if ($instance === null)
            $instance = new Singleton();
        xdebug_debug_zval('instance');
        return $instance;
    }
    private function __construct() { }
}
$a = Singleton::getInstance();
xdebug_debug_zval('a');
$b = Singleton::getInstance();
xdebug_debug_zval('b');

This gives:

instance: (refcount=2, is_ref=1), object(Singleton)[1]

a: (refcount=1, is_ref=0), object(Singleton)[1]

instance: (refcount=2, is_ref=1), object(Singleton)[1]

b: (refcount=1, is_ref=0), object(Singleton)[1]

So it has the disadvantage a new zval will be created on each call. This is not particularly serious, so if you prefer it, go ahead.

The reason a zval separation is forced is that inside getInstance, $instance is a reference (in the sense of =&, and it has reference count 2 (one for the symbol inside the method, another for the static storage). Since getInstance doesn't return by reference, the zval must be separated -- for the return, a new one is created with reference count 1 and the reference flag clear.


The cleanest solution is to remove the singleton logic from the class itself (because it's something not related to the job of the class itself).

For an interesting implementation see this: http://phpgoodness.wordpress.com/2010/07/21/singleton-and-multiton-with-a-different-approach/


After some playing around the best method I can think of is like so:

Create a file called SingletonBase.php and include it in root of your script!

The code is

abstract class SingletonBase
{
    private static $storage = array();

    public static function Singleton($class)
    {
        if(in_array($class,self::$storage))
        {
            return self::$storage[$class];
        }
        return self::$storage[$class] = new $class();
    }
    public static function storage()
    {
       return self::$storage;
    }
}

Then for any class you want to make a singleton just add this small single method.

public static function Singleton()
{
    return SingletonBase::Singleton(get_class());
}

Here is a small example:

include 'libraries/SingletonBase.resource.php';

class Database
{
    //Add that singleton function.
    public static function Singleton()
    {
        return SingletonBase::Singleton(get_class());
    }

    public function run()
    {
        echo 'running...';
    }
}

$Database = Database::Singleton();

$Database->run();

And you can just add this singleton function in any class you have and it will only create 1 instance per class.

Just another idea you can also do

if(class_exists('Database'))
{
   $Database = SingletonBase::Singlton('Database');
}

and at the end of your script you can do some dfebugging if you need too,

at the end of your script you can just do

foreach(SingletonBase::storage () as $name => $object)
{
     if(method_exists("debugInfo",$object))
     {
         debug_object($name,$object,$object->debugInfo());
     }
}

so this method will be great for a debugger to get access to all classes and object states that have been initialized

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号