PHP5 stdClass assignment gotcha

In PHP 5 all objects are assigned by reference, meaning the = and => operators don’t make a copy but instead cause both variables to refer to the same place in the symbol table. This is useful, because objects are arbitrarily complex structures for which a bit-wise copy of the memory allocated might well not make a valid new object. If you want a copy, use the clone operator, implementing the method __clone() for the class if necessary.

This has some nice consequences; class object creation now looks like:

$foo = new Bar();

rather than (in PHP4)

$foo =& new Bar();

By the way the comparison operator == will return true if two objects have the same attributes and values and are instances of the same class, whereas the identity operator === will return true only if the two objects refer to the same instance of the same class.

PHP5 is not an Object Oriented Language, but just the same it is being used as and OOL, and it has language constructs which facilitate that. If you’re like me, though, you tend to use stdClass objects not as simple class instances but as PHP’s answer to what is called a record or struct in other languages. This can cause confusion, especially when assignment doesn’t go quite as planned.

Here’s the thing:

function works() {
    $coord->x = rand();
    $coord->y = rand();
    return $coord;
}

Each time works() is called a new $coord instance is created and a reference to it is returned from the function. When $coord goes out of scope that returned reference is the only one.

function fails() {
    $stars = array();
    for( $i = 0; $i < 10; $i++ ) {
        $coord->x = rand();
        $coord->y = rand();
        $stars[] = $coord;
    }
    return $stars;
}

The problem here is that each element of $stars[] is a reference to the same object, the same one that’s called $coord. So fails() will return an array of ten identical coordinates, the ones produced in the last loop iteration.

The solution is simple – use clone. The function becomes:

function fails() {
    $stars = array();
    for( $i = 0; $i < 10; $i++ ) {
        $coord->x = rand();
        $coord->y = rand();
        $stars[] = clone $coord;
    }
    return $stars;
}