PHP 7 changes to foreach: Can I still delete items in they array on which I'm iterating?

userlite picture userlite · Jan 3, 2016 · Viewed 13.1k times · Source

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.

Answer

Machavity picture Machavity · Jan 3, 2016

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.