4

I've noticed a weird behavior with singletons in PHP there's no better way to explain this but with an example.

Let's say I have the following singleton class:

class Singleton
{
    protected function __construct()
    {
        // Deny direct instantion!
    }
    protected function __clone()
    {
        // Deny cloning!
    }
    public static function &Instance()
    {
        static $Instance;

        echo 'Class Echo'.PHP_EOL;
        var_dump($Instance);

        if (!isset($Instance)) {
            $Instance = new self;
        }

        return $Instance;
    }
}

And the following function:

function Test($Init = FALSE)
{
    static $Instance;

    if ($Init === TRUE && !isset($Instance)) {
        $Instance =& Singleton::Instance();
    }

    echo 'Function Echo'.PHP_EOL;
    var_dump($Instance);

    return $Instance;
}

And when I use the following:

Test(TRUE);
Test();
Singleton::Instance();

The output is:

Class Echo
NULL
Function Echo
object(Singleton)#1 (0) {
}
Function Echo
NULL
Class Echo
object(Singleton)#1 (0) {
}

As you can see the saved reference inside the function is lost after the execution even though the variable is static Also notice that the static variable inside the class method is working fine.

Is this supposed to be normal or I'm doing something wrong?

Micha
  • 5,117
  • 8
  • 34
  • 47
SLC
  • 2,167
  • 2
  • 28
  • 46
  • Side Note: you also want to implement `__wakeup()` to prevent unserialization. But you do know [Singletons are mostly useless](http://stackoverflow.com/questions/4595964/who-needs-singletons/4596323#4596323), right? – Gordon Jul 31 '13 at 07:55

2 Answers2

6

This behaviour is documented:

The Zend Engine 1, driving PHP 4, implements the static and global modifier for variables in terms of references. For example, a true global variable imported inside a function scope with the global statement actually creates a reference to the global variable. This can lead to unexpected behaviour.

This behaviour hasn't changed since ZE1 and the solution is simply to not assign a reference to a static variable, so:

$Instance = Singleton::Instance();

Update

I've simplified the issue below:

function test2($init = false, $value = null)
{
  static $test;

  if ($init) {
    $test =& $value;
  }

  var_dump($test);
}

$v = 1;

test2(true, $v);
test2();

Output:

int(1)
NULL
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • And I thought I was doing something wrong. Thanks for the extended explanation. – SLC Jul 31 '13 at 17:26
  • Also, in PHP >= PHP 5, [objects are by default passed by value of reference](http://stackoverflow.com/questions/2715026/are-php5-objects-passed-by-reference). This means that the statements `$Instance =& Singleton::Instance()` and `$Instance = Singleton::Instance()`, while not exactly the same, will produce a functionally identical result. – XedinUnknown Jul 17 '15 at 18:02
2

This is the normal pattern for a singleton in php. Note the $instance is a static property not a static variable within a function.

I am having trouble figuring out why but your above code can be fixed by doing one of the following.

1) Remove the reference assignment in the Test Function ($Instance =& Singleton::Instance(); becomes $Instance = Singleton::Instance();)

2) Remove the return by reference in the Instance Method (public static function &Instance() becomes public static function Instance())

3) Or both 1 & 2.

class Singleton{
    protected static $instance;

    protected function __construct(){

    }

    protected function __clone(){
        throw new Exception("Cannot clone a Singleton");
    }

    public static function getInstance(){
        if(!isset(static::$instance)){
            static::$instance = new static();
        }
        return static::$instance;
    }
}
Orangepill
  • 24,500
  • 3
  • 42
  • 63
  • I've only used the $instance variable as a static variable inside the method to show that the static variable inside the method retains the value even after the function scope ends while the static variable inside the test function is looses it's value when the functions scope ends and the value becomes NULL – SLC Jul 31 '13 at 05:20
  • @SanduLiviuCatalin Updated with a solution... still looking for an explaination. – Orangepill Jul 31 '13 at 05:41
  • @SanduLiviuCatalin Jack has the why here... it's basically a documented behavior/bug – Orangepill Jul 31 '13 at 06:11
  • Wouldn't that create a copy of the object? – SLC Jul 31 '13 at 17:28