Thursday, July 15, 2004

PHP 5 Object References

PHP 5 seems to mostly fix the most glaring problem in PHP 4, its non-intuitive handling of references. However, this issue is more complicated than it sounds. For starters, here is one of the unit tests from PHP 5:

<?php

class Foo {
    var $name;
    
    function Foo() {
        $this->name = "I'm Foo!\n";
    }
}

$foo = new Foo;
echo $foo->name;
$bar = $foo;
$bar->name = "I'm Bar!\n";

// In ZE1, we would expect "I'm Foo!"
echo $foo->name;

?>

With PHP 4, this prints:

I'm Foo!
I'm Foo!

With PHP 5, it prints:

I'm Foo!
I'm Bar!

To get this result in PHP 4, you need to use the & operator:

<?php

class Foo {
    var $name;
    
    function Foo() {
        $this->name = "I'm Foo!\n";
    }
}

$foo = new Foo;
echo $foo->name;
$bar = &$foo;
$bar->name = "I'm Bar!\n";

// In ZE1, we would expect "I'm Foo!"
echo $foo->name;

?>

This is because PHP 4 copies objects by default; using & makes it assign by reference instead of by copy. Copying by default is pretty annoying if you’re used to other languages, but it does have the virtue of consistency. Assignment without & copies for both scalars and objects; assignment with & uses references for both scalars and objects.

I’ve had trouble pinning down exactly how PHP 5 changes this. As far as I can tell, object references are not mentioned in the official PHP 5 ChangeLog. One article I found says this:

In PHP4, references were a subject of confusion, the default behaviour, when passing objects around, being to copy rather than reference the object.

With PHP5 the default behaviour is now to pass objects by reference (using the same approach as Java, in other words). What does this mean to you, the developer? Well, if you didn't understand how references worked in PHP4, you can now pretty much forget the subject completely.

But I find this confusing because the most confusing aspect of PHP 4 references goes unmentioned. In PHP 4, this code:

<?php

class Label {
    var $text;
    function Label($text) {
        $this->text = $text;
    }
}

$foo = new Label("foo");
$bar =& $foo;
$bar = new Label("bar");
print_r($foo);

?>

prints:

label Object
(
    [text] => bar
)

This odd result is explained by the PHP manual thusly:

References in PHP are a means to access the same variable content by different names. They are not like C pointers, they are symbol table aliases. Note that in PHP, variable name and variable content are different, so the same content can have different names. The most close analogy is with Unix filenames and files - variable names are directory entries, while variable contents is the file itself. References can be thought of as hardlinking in Unix filesystem.

As noted, in PHP 4 objects and scalars were treated alike, hence this other example from the PHP manual:

$a = 1; 
$b =& $a; 
$b = 2; 
print "$a $b";
// 2 2

When I first read about PHP 5, I thought that it made & implicit for objects. In other words, I thought that:

<?php

class Label {
    var $text;
    function Label($text) {
        $this->text = $text;
    }
}

$foo = new Label("foo");
$bar = $foo;
$bar = new Label("bar");
print_r($foo);

?>

would print:

label Object
(
    [text] => bar
)

in PHP 5. I thought this because I kept reading that you don’t have to type & in PHP 5. However, it now appears that I was wrong. My tests with the just-released PHP 5 show that & works in the same, arguably broken, “hardlink” kind of way with both PHP 4 and PHP 5. The difference is that in PHP 4, if you don’t use &, a copy of the object is made. In PHP 5, if you don’t use &, a Java-style reference is used. Thus, this code:

<?php

class Label {
    var $text;
    function Label($text) {
        $this->text = $text;
    }
}

$foo = new Label("foo");
$bar = $foo;
$bar = new Label("bar");
print_r($foo);

?>

prints:

label Object
(
    [text] => foo
)

in both PHP 4 and PHP 5. And this code:

<?php

class Label {
    var $text;
    function Label($text) {
        $this->text = $text;
    }
}

$foo = new Label("foo");
$bar =& $foo;
$bar = new Label("bar");
print_r($foo);

?>

prints:

label Object
(
    [text] => bar
)

in both PHP 4 and PHP 5.

The bad news is that PHP 5 is in some sense less consistent than before. There are now three different kinds of assignment (copy, hardlink, and reference copy), and scalars and objects are now treated differently. The good news is that, if I understand this correctly, you can banish & from all your PHP 5 code and then use it like Java.

24 Comments

It's written in the doc :
http://www.php.net/manual/en/migration5.oop.php

" In PHP 5 there is a new Object Model. PHP's handling of objects has been completely rewritten, allowing for better performance and more features. In previous versions of PHP, objects were handled like primitive types (for instance integers and strings). The drawback of this method was that semantically the whole object was copied when a variable was assigned, or pass as a parameter to a method. In the new approach, objects are referenced by handle, and not by value (one can think of a handle as an object's identifier). "

I almost linked to that article at http://www.php.net/zend-engine-2.php, but I didn't think it added anything to the SitePoint one.

"In the new approach, objects are referenced by handle, and not by value"

That doesn't really specify what's happening.

You explain in your first example how using & can make PHP4 code perform like PHP5. My question, for migration issues, is how do you get the PHP5 implementation to behave like the output from ZE1?

Regards making PHP5 behave along ZE1 lines:

One word: __clone()

Look it up and all should be revealed.

The PHP function for making a clone of an object is "clone()" and not __clone()

EXAMPLE - To get the PHP5 implementation to behave like the output from ZE1:

$bar = clone($foo);

note: okay in php 5.0.4

I am not sure there IS such a thing as "hard-link", in either version of PHP.

In this code:

$foo = new Label("foo");
$bar =& $foo;
$bar = new Label("bar");
print_r($foo);

The reason that $foo has "foo" in it instead of "bar" is actually quite simple. You ASSIGNED the reference of the $foo object to $bar, and then you REASSIGNED another reference (object) to $bar with the new operator and the creation of the second Label object.

If I understand you correctly, you were expecting that $bar is made a hard link to the object that $foo is also pointing to, so that creating a new object and assigning that to $bar should overwrite the object that $bar was referring to, thus affecting any other references to that original object as well. This is not the case, in either version of PHP, and to the best of my knowledge, there's NO way to do that at all. As a matter of fact, that type of behavior, in any language without garbage collection, such as C/C++, would cause a HUGE memory leak, because in your example, the original Label object would still exist somewhere floating around in memory, but neither $foo nor $bar would point to it.

Consider a simpler example:

$a = "abc";
$b = &$a;
$b = "def";
print_r($a); // WILL print abc, in either version
print_r($b); // WILL print def, in either version

The second instance of $b appearing as an "l-value" (on the left-hand side of the = assignment operator) overwrites the first assignment where it was made to point, as a reference, to $a.

Again, I don't think there is anyway to make a hard-link to $a such that even re-assigning that hard-link affects $a. This to me is counter-intuitive to any programming language i've been in contact with. :)

Kyle: I agree that it's counter-intuitive, but observe what happens when I run your example:

moya$ php --version
PHP 4.3.10 (cli) (built: Mar 20 2005 19:53:06)
Copyright (c) 1997-2004 The PHP Group
Zend Engine v1.3.0, Copyright (c) 1998-2004 Zend Technologies
moya$ cat test.php 
<?php
$a = "abc";
$b = &$a;
$b = "def";
print_r($a);  // WILL print abc, in either version
print_r($b);  // WILL print def, in either version
?>
moya$ php test.php 
defdefmoya$ 

I found this which answered my question about passing an object instance to a function. It is example 19.2 on this page
http://ca.php.net/manual/en/language.oop5.basic.php

"When assigning an already created instance of an object to a new variable, the new variable will access the same instance as the object that was assigned. This behaviour is the same when passing instances to a function. A new instance of an already created object can be made by cloning it."

@ Amir

__clone() is the method implemented by a class that is used for cloning when the function is used. If you don't implement __clone() on one of your classes it still gets a default behavior which is to set up all the member variables with copies of the values from the original object.

So, both are correct. Also note that in PHP 5 clone became a keyword and can be used this way:

$bar = clone $foo;

I spent pretty much time once, when I tried to figure out how the references in php4 really work. Assignment by reference is creating new name for the same data location. Everything what happens to a variable also happens to references (and vice-versa). The way to unlink variable name (assigned by reference) from the data location it points to is to unset($reference).

Example:
[mco@inox ~]$ php.cli --version
PHP 5.1.6 (cli) (built: Sep 5 2006 11:48:58)
Copyright (c) 1997-2006 The PHP Group
Zend Engine v2.1.0, Copyright (c) 1998-2006 Zend Technologies
[mco@inox ~]$ cat test.php

[mco@inox ~]$ php.cli test.php
defdefdefghi[mco@inox ~]$

i does not agree with kyle.I think they act as the hard links

I found an interesting article which exactly describes what happens internaly on assigning values:
http://derickrethans.nl/files/phparch-php-variables-article.pdf
Objects aren't mentioned there, but it IMHO uses the same mechanism.

holy cow, so in PHP5, $obj = $obj2
and $obj =& $obj2 are different things...

so my understanding is that $obj = $obj2 in PHP 5 is the same as in Ruby or Java.

and that $obj =& $obj2
is just like, treat us as synonyms...

When assigning an already created instance of an object to a new variable, the new variable will access the same instance as the object that was assigned. This behaviour is the same when passing instances to a function. A new instance of an already created object can be made by cloning it

This might be a late comment, but I believe you're confused between passing and assigning.
example:
$a = 'frop';
$a = $b;
Will put the value $a has into $b.
$a =& $b;
Will make a reference, so that $a and $b both reference to the same value.

Passing is a whole other thing.
function foo( $text ) {
//bla
}
Will copy a string/int/double (scalars).
This is passing something to a function.
in PHP5 the default action is pass by reference, FOR OBJECTS.
So, when you pass an object (to an function or something), then it will make a reference by default.
So just keep in mind that there is a difference between assigning and passing.

I think there is a difference between
$a =& $b;
and
$a = &$b;

[...] I thought, with php5, '&' was no longer needed... The solution is explained in this article: http://mjtsai.com/blog/2004/07/15/ph...ct-references/ It appears that my problem boils down to a misapprehension of what "passing an object by [...]

In PHP5, assigning objects to variables creates a copy-on-write reference to the object. However, the properties of that object are references.

$obj = new StdClass; // this is a copy-on-write
$obj->property = 'hi'; // the property is a reference

$obj2 = $obj; // copy on write

$obj2->property = 'bye'; // $obj->property also changed since it is a reference

$obj2 = null; // $obj2 changed, thus it has to be copied, $obj stays the same

$property = $obj->property; // copy-on-write reference

$property = null; // $property changed, thus copied, does not affect $obj->property;

In fact as far as I can see, all assignment is copy-on-write, even __clone(), unless specifically referenced with & or an object property (that has more then one reference to it).
The notion that PHP5 objects are assigned by reference, or passed by reference is not true, only the properties of that object are. All variables seem to be passed or assigned as copy-on-write unless using &.

The difference can be pin pointed as follows:

<?php

class Label
{
    var $text;
    function setLabel($text)
    {
        $this->text = $text;
    }
}

$foo = new Label();
$bar1 = $foo;
$bar2 =& $foo;

$foo->setLabel("foo");


echo "Original values: all are same\n";
echo "foo:".$foo->text." bar1:".$bar1->text." bar2:".$bar2->text."\n";


$bar1=new Label();
$bar1->setLabel("bar1");

echo "After bar1 was reassigned: notice that foo does not change its value:
foo and bar1 are not the same variable\n";
echo "foo:".$foo->text." bar1:".$bar1->text." bar2:".$bar2->text."\n";

$bar2=new Label();
$bar2->setLabel("bar2");

echo "After bar2 was reassigned: notice that foo changes its value: foo and
bar2 are the same variable\n";
echo "foo:".$foo->text." bar1:".$bar1->text." bar2:".$bar2->text."\n";
?>

PHP 5.2.9 (cli) (built: Apr 15 2009 09:30:33)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2009 Zend Technologies

Original values: all are same
foo:foo bar1:foo bar2:foo
After bar1 was reassigned: notice that foo does not change its value: foo
and bar1 are not the same variable
foo:foo bar1:bar1 bar2:foo
After bar2 was reassigned: notice that foo changes its value: foo and bar2
are the same variable
foo:bar2 bar1:bar1 bar2:bar2

"Copying by default is pretty annoying if you’re used to other languages, but it does have the virtue of consistency."

The keyword is "other". Your point is pointless.

As pointless as saying: "Using spanish words that need gender modifiers is pretty annoying when you're use to non-gender languages like english."

As if one is good and the other is bad. Different isn't bad. You basically wasted a sentence saying that a different language is different.

@jolopy Different is bad if it leads to errors. Suppose that PHP used != as its equality operator and == as its inequality operator, the opposite from the way other languages work. Would that just be “different”?

[...] a b “PHP 5 Object References”. mjtsai. http://mjtsai.com/blog/2004/07/15/php-5-object-references/. Retrieved [...]

[...] a b “PHP 5 Object References”. mjtsai. Retrieved [...]

Stay up-to-date by subscribing to the Comments RSS Feed for this post.

Leave a Comment