27

The PHP 7 Backward-Incompatible Changes Document says the following about foreach:

When used in the default by-value mode, foreach will now operate on a copy of the array being iterated rather than the array itself. This means that changes to the array made during iteration will not affect the values that are iterated.

I'm trying to understand what this means, and my main question is whether this code will work the same in PHP 7 as it does in PHP 5.6?

foreach($array as $elementKey => $element) {
    if ($element == 'x') {
        unset($array[$elementKey]);
    }
}

My two questions are:

  1. Will this code still work?

  2. If so, can you explain (maybe by example) what this new change in PHP 7 means?

Edit

I've been re-reading the doc statement. I'm thinking that what it means is that, if you change values of items lower down in the array, those changes won't be there when you get to those items in the iteration. Example:

$array = ['x', 'y', 'z'];

$new = [];

foreach($array as $element) {
    if ($element == 'x') {
        $array[2] = 'a';
    }

    $new[] = $element;
}

print_r($new);

However, when I run this example it doesn't seem to show any difference in PHP versions (although I've never used this tool before, so I'm not sure how it works).

I realize that if I do it by reference, I'll get an a in new. Otherwise I won't. But this seems to be the case in both versions.

What I really need to know is what is the incompatibility (by example)?

Edit 2

The answer link suggested by @NikiC provides the rest of the story I was looking for:

In most cases this change is transparent and has no other effect than better performance. However there is one occasion where it results in different behavior, namely the case where the array was a reference beforehand:

$array = [1, 2, 3, 4, 5];
$ref = &$array;
foreach ($array as $val) {
    var_dump($val);
    $array[2] = 0;
}
/* Old output: 1, 2, 0, 4, 5 */
/* New output: 1, 2, 3, 4, 5 */

Previously by-value iteration of reference-arrays was special cases. In this case no duplication occurred, so all modifications of the array during iteration would be reflected by the loop. In PHP 7 this special case is gone: A by-value iteration of an array will always keep working on the original elements, disregarding any modifications during the loop.

This answer explains the rare "special case" where things work different between versions in regarding foreach operating on a copy of the array.

Community
  • 1
  • 1
userlite
  • 479
  • 1
  • 5
  • 13

1 Answers1

23

All it means is that you now have to explicitly say you want to reference the array you're iterating now.

In your example code, however, you're referencing the root array anyways so it will work whether or not you pass by reference.

<?php
$array = ['x', 'y', 'z'];

foreach($array as $elementKey => $element) {
    if ($element=='x') {
        unset($array[$elementKey]);
    }
}

var_dump($array); // lists 'y' and 'z'

A better example. In this case, we're changing the value inside the foreach without a reference. As such, the local changes will be lost:

<?php
$array = ['x', 'y', 'z'];

foreach($array as $element) {
    if ($element=='x') {
        $element = 'a';
    }
}

var_dump($array); // 'x', 'y', 'z'

Vs by reference, where we declare the $element to be a reference to the array element:

<?php
$array = ['x', 'y', 'z'];

foreach($array as &$element) {
    if ($element=='x') {
        $element = 'a';
    }
}

var_dump($array); // 'a', 'y', 'z'

Here's a demo of this code running on various versions of PHP.

Will
  • 24,082
  • 14
  • 97
  • 108
Machavity
  • 30,841
  • 27
  • 92
  • 100
  • Thanks Machavity. Your examples are helpful and make sense to me. I'm still wondering what the difference is between 5.6 and 7. It seems to me like all three of these examples are doing what I would expect in PHP 5.6 – userlite Jan 03 '16 at 04:11
  • 2
    Indeed, all three examples here act the same in 5 and 7. – rjdown Jan 03 '16 at 04:21
  • I think the main concern I came here for has been resolved, which was that I wanted to be sure that the concept of unsetting items in the array I'm iterating on is not going to be different in PHP 7. My secondary concern is just understanding what actually _is_ different. Most of the other incompatibilities have examples in the doc, but this one doesn't. – userlite Jan 03 '16 at 04:31
  • 3
    @userlite See http://stackoverflow.com/questions/10057671/how-does-foreach-actually-work/14854568#14854568, section PHP 7 > Array duplication > Paragraph 2 and the following example. – NikiC Jan 03 '16 at 12:55
  • 1
    @Machavity: your code example (once fixed to not use the array literal so it runs on older versions of PHP) runs exactly the same for all versions tested when I run it; and if one tests with or without the reference syntax, the test is the same across all versions too. What difference are *you* seeing for *the same code* across all those versions of PHP? – Adam Cameron Jan 03 '16 at 13:02
  • @AdamCameron See the link NikiC's comment (he understands PHP far better than I). It turns out there's an edge case involving the array being a reference by something else and not behaving as you would expect. – Machavity Jan 03 '16 at 22:27
  • 2
    Sure, I understand the issue. My point was more that I just don't think your answer - for all its upvoting and being accepted - is actually the answer to the question that was asked. It answers perhaps a different question ;-) – Adam Cameron Jan 04 '16 at 00:32