views:

54

answers:

3

I'm using PHP 5.2.14 and PearLog 1.12.3. The latest documentation from the singleton method in Log.php (PEARLog) states:

You MUST call this method with the $var = &Log::singleton()syntax. Without the ampersand (&) in front of the method name, you will not get a reference; you will get a copy.

However, doing so generates the following warning:

STRICT NOTICE: Only variables should be assigned by reference


The source for this function is:

public static function singleton($handler, $name = '', $ident = '',
                                 $conf = array(), $level = PEAR_LOG_DEBUG)
{
    static $instances;
    if (!isset($instances)) $instances = array();

    $signature = serialize(array($handler, $name, $ident, $conf, $level));
    if (!isset($instances[$signature])) {
        $instances[$signature] = Log::factory($handler, $name, $ident,
                                              $conf, $level);
    }

    return $instances[$signature];
}

If I remove the & and use just:

$var = Log::singleton()

then I no longer get the warning. Also, if I do

$var = Log::singleton();
$var2 = Log::singleton();

then $var === var2 evaluates to true.


Question: Which is correct: the API documentation or the warning? (If the function returns an object, isn't it a reference anyway? Why would I need the ampersand ?

+1  A: 

The way to deal with references changed a bit in PHP in PHP 5. Now they want the called function to decide to return by reference or by value, not the caller. But usually PHP can sort this out by itself - like in your case it does detect that these two are the same object.

For more information about your E_STRICT check out the manual: http://www.php.net/manual/en/language.references.whatdo.php and how the pear function should be implemented: http://www.php.net/manual/en/language.references.return.php . (In my oppinion most parts of pear are outdated, the zend framework covers most of pear now.)

Edit: Large example of references:

error_reporting(E_STRICT);
ini_set('display_errors', 1);

class Settings
{
    private static $_instance;

    public static function getInstance()
    {
        if(self::$_instance == null)
        {
            self::$_instance = new Settings();
        }

        return self::$_instance;
    }

    public static function &getInstanceByRef()
    {
        if(self::$_instance == null)
        {
            self::$_instance = new Settings();
        }

        return self::$_instance;
    }

    private $counter = 0;

    private function Settings()
    {
    }

    public function getCounter()
    {
        return $this->counter;
    }

    public function setCounter($value)
    {
        $this->counter = $value;
    }
}

$settings1 = Settings::getInstance();
$settings2 = Settings::getInstance();

echo $settings1->getCounter(); // 0
echo $settings2->getCounter(); // 0

$settings1->setCounter(42);

echo $settings1->getCounter(); // 42
echo $settings2->getCounter(); // 42

$settings3 = &Settings::getInstanceByRef(); // ref to private static $_instance !
$settings4 = &Settings::getInstanceByRef(); // ref to private static $_instance !

echo $settings3->getCounter(); // 42
echo $settings4->getCounter(); // 42

$settings3 = 5;
$settings5 = Settings::getInstance();
echo $settings5; // 5 

As you can see even without refence getInstance is handled as reference. If you want to use references, both the caller and the called function must be marked as reference.

As a warning: return by reference can cause hard to find bugs: Returning by reference allows me to overwrite the private instance holding var. The expected behaviour in PHP would be that $settings3 is 5 but not the private static $_instance; This can result in very unpredictable code.

Fge
@Fge: So will `Log::singleton()` always return the same object in PHP 5 even though the function is not declared to return a reference?
JRL
I added an example at my answer. Basically yes - it does. See @Jeremy 's answer for further details :)
Fge
+4  A: 

The way that objects are passed fundamentally changed in PHP5. In PHP4 they were always passed by value, meaning that a function or method that returned an object was actually passing a copy of the object back. This led to the use of the '&' operator, forcing the function to return an object by reference. In PHP5 objects are always passed by reference. To create a copy of an object you have to use the clone operator.

From having a very quick look at the source for the log package it appears that it maintains compatibility with PHP4. I don't think that you need the ampersand. PHP5 will return a reference to the object. Your test of '$var === $var2' has proved that the method returns an object and that the object is a reference to one object. If they were copies of an object the identity comparison would evaluate to false.

Jeremy
+2  A: 

The warning is correct and the API documentation is outdated, objects are returned by reference since PHP5.

Adam Byrtek