PHP 5.5.12. Consider this:
<?php
$a = [ 'a', 'b', 'c' ];
foreach($a as &$x) {
$x .= 'q';
}
print_r($a);
This, as expected, outputs:
Array
(
[0] => aq
[1] => bq
[2] => cq
)
Now consider:
<?php
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x) {
$x .= 'q';
}
print_r($a);
function z($a)
{
return $a;
}
This outputs:
Array
(
[0] => aq
[1] => bq
[2] => cq
)
(!) But wait a minute. $a is not being passed by reference. Which means I should be getting a copy back from z(), which would be modified, and $a ought to be left alone.
But what happens when we force PHP to do its copy-on-write magic:
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x) {
$x .= 'q';
}
print_r($a);
function z($a)
{
$a[0] .= 'x';
return $a;
}
For this, we get what I would expect:
Array
(
[0] => a
[1] => b
[2] => c
)
EDIT: One more example...
$a = [ 'a', 'b', 'c' ];
$b = z($a);
foreach($b as &$x) {
$x .= 'q';
}
print_r($a);
function z($a)
{
return $a;
}
This works as expected:
Array
(
[0] => a
[1] => b
[2] => c
)
Is there a rational explanation for this?
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:
Fixed with this commit; this will become available in 5.5.18 and 5.6.2.
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
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.
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
Only Hack handles the situation correctly as it currently stands.
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 too
function &z(&$a)
{
return $a;
}
Demo
To avoid changing the original array, for now, you have the following options:
foreach
;If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With