Is there a rational explanation for this PHP call-by-value behavior? Or PHP bug? Is there a rational explanation for this PHP call-by-value behavior? Or PHP bug? arrays arrays

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'];

Proof

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;}

Demo

Other fixes

To avoid changing the original array, for now, you have the following options:

  1. Assign the function result into a temporary variable before the foreach;
  2. Don't use references;
  3. 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.