0

I'm a little confused on the behavior of for loops when iterating over an array of objects.

The PHP documentation says

foreach (array_expression as $key => $value)

In order to be able to directly modify array elements within the loop precede $value with &. In that case the value will be assigned by reference.

Based on that, I was under the impression $value was a copy (by value) of the object in the array. However, to verify I just ran this test:

class test {
    public $foo = 1;
    function __construct($foo) {
        $this->foo = $foo;
    }
}

$testArray = array();
$testArray[] = new test(1);
$testArray[] = new test(2);
$testArray[] = new test(3);;

echo '$testArray' . '<pre>' . PHP_EOL;
print_r($testArray);
echo PHP_EOL . '</pre>';

foreach ($testArray as $test) {
    if ($test->foo == 2) {
        $temp = $test;
    }
}

$temp->foo = 'copied by value or reference?';

echo '$testArray' . '<pre>' . PHP_EOL;
print_r($testArray);
echo PHP_EOL . '</pre>';

Surprisingly, the output is:

$testArray
Array
(
    [0] => test Object
        (
            [foo] => 1
        )

    [1] => test Object
        (
            [foo] => 2
        )

    [2] => test Object
        (
            [foo] => 3
        )

)

$testArray
Array
(
    [0] => test Object
        (
            [foo] => 1
        )

    [1] => test Object
        (
            [foo] => copied by value or reference?
        )

    [2] => test Object
        (
            [foo] => 3
        )

)

My confusion is that when I did the $temp = $test assignment it did a copy by reference to the original object in the array, but I thought $test had been copied by value according to the documentation?


OK, so maybe I just didn't pay attention to the fine print:

In order to be able to directly modify array elements within the loop precede $value with &. In that case the value will be assigned by reference.

So lets try a test where we modify the object inside the loop:

class test {
    public $foo = 1;
    function __construct($foo) {
        $this->foo = $foo;
    }
}

$testArray = array();
$testArray[] = new test(1);
$testArray[] = new test(2);
$testArray[] = new test(3);;

echo '$testArray' . '<pre>' . PHP_EOL;
print_r($testArray);
echo PHP_EOL . '</pre>';

foreach ($testArray as $test) {
    if ($test->foo == 2) {
        $temp = $test;
        $temp->foo = 'copied by value or reference?';
    }
}

echo '$testArray' . '<pre>' . PHP_EOL;
print_r($testArray);
echo PHP_EOL . '</pre>';

Output:

$testArray
Array
(
    [0] => test Object
        (
            [foo] => 1
        )

    [1] => test Object
        (
            [foo] => 2
        )

    [2] => test Object
        (
            [foo] => 3
        )

)

$testArray
Array
(
    [0] => test Object
        (
            [foo] => 1
        )

    [1] => test Object
        (
            [foo] => copied by value or reference?
        )

    [2] => test Object
        (
            [foo] => 3
        )

)

The result is the same. It appears the object is copied by reference, even though the documentation clearly insinuates they are copied by value.

What gives?

Nate
  • 26,164
  • 34
  • 130
  • 214
  • Aren't objects always given by reference in php? Contrary to scalar variables... It's the case for function parameters at least, or am i mistaken? – fpierrat Dec 28 '14 at 02:29
  • @fpierrat Then why does the documentation say to include the reference symbol `&` before `$value` to copy by reference? – Nate Dec 28 '14 at 02:31
  • 1
    For scalar elements. Abt objects, variables and references, see http://php.net/manual/fr/language.oop5.references.php (maybe replace fr with en...) – fpierrat Dec 28 '14 at 02:33

1 Answers1

3

As of PHP 5, an object variable doesn't contain the object itself as value anymore. It only contains an object identifier which allows object accessors to find the actual object. When an object is sent by argument, returned or assigned to another variable, the different variables are not aliases: they hold a copy of the identifier, which points to the same object.

http://php.net/manual/en/language.oop5.references.php

Edit: For the first time I just had the reverse issue, I needed to copy an object by value.
As simply as $new_obj = clone $original_obj if your object is simple... it soon becomes a nightmare if you have an object containing other objects, eventually in arrays or sub-arrays! Several __clone() magic functions in differents classes and some headaches for some tricky cases...
I finally wiped out all this clone and __clone() stuff and chose following solution:

$new_obj = unserialize(serialize($original_obj));  

Here interesting pro/con-comments about this solution, which in my case was the best one - or at least the easiest one :-) : How do I create a copy of an object in PHP?

Community
  • 1
  • 1
fpierrat
  • 739
  • 7
  • 25
  • Nice, thanks for the quote and the link. So there's a lot of legacy code out there now using `&` before objects and it's not necessary any more.. – Nate Dec 28 '14 at 15:23