Is there a rational explanation for this PHP call-by-value behavior? Or PHP bug?
Update
Bug 67633 has been opened to address this issue. The behaviour has been changed by this commit in an effort to remove reference restrictions from foreach.
From this 3v4l output you can clearly see that this behaviour has changed over time:
Update 2
Fixed with this commit; this will become available in 5.5.18 and 5.6.2.
PHP 5.4
Prior to PHP 5.5 your code would actually raise a fatal error:
Fatal error: Cannot create references to elements of a temporary array expression
PHP 5.5 - 5.6
These versions do not perform copy-on-write when the function result is used directly inside the foreach
block. As such, the original array is now used and changes to the elements are permanent.
I personally feel that this is a bug; copy-on-write should have taken place.
PHP > 5.6
In the phpng branch, which is likely to become the basis of a next major version, constant arrays are made immutable so the copy-on-write is correctly performed only in this case. Declaring the array like below will exhibit the same issue with phpng:
$foo = 'b';$a = ['a', $foo, 'b'];
Hack (HHVM)
Only Hack handles the situation correctly as it currently stands.
The right way
The documented way of using the function result by reference is this:
$a = [ 'a', 'b', 'c' ];foreach(z($a) as &$x) { $x .= 'q';}print_r($a);// indicate that this function returns by reference // and its argument must be a reference toofunction &z(&$a){ return $a;}
Other fixes
To avoid changing the original array, for now, you have the following options:
- Assign the function result into a temporary variable before the
foreach
; - Don't use references;
- Switch to Hack.
In this example, the function z does nothing. It doesn't copy or clone anything therefore the response from z() will be the same as not calling at all. You are simply returning the object passed in and therefore the response is as expected.
<?php$a = [ 'a', 'b', 'c' ];foreach(z($a) as &$x) { $x .= 'q';}print_r($a);function z($a){ return $a;}
Thiis is easier to demonstrate using objects as they are given a system ID:
<?php$obj = new stdClass();$obj->name = 'foo';function z($a){ $a->name = 'bar'; return $a;}var_dump($obj);var_dump(z($obj));
The output for this is:
object(stdClass)#1 (1) { ["name"]=> string(3) "foo"}object(stdClass)#1 (1) { ["name"]=> string(3) "bar"}
Both objects have the ID as "1" which shows they are not copies or clones.