Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected behaviour in foreach while looping and unsetting in ArrayObject. An item is ignored

(examples at the bottom!!!)

we have just upgrade our backend to PHP7 and after that, we have found a bug in our code related to an ArrayObject.

The code just loops over a copy of an Object (type native ArrayObject). The foreach iterates by value.

The purpose of the code is to filter some values that you don't need. In the example, if the iterated value is "two" or "three", unset it. I have tried it using iterator instead of the copied value and without the iterator.

Results:

- Iterator

  • PHP 5.6: works as expected, the returned value is the array without the values "two" and "three"
  • PHP 7: it only removes "two" and seems that the item with value "three" is not evaluated (see echo inside the loop, it doesn't print "three")

- No Iterator

  • PHP 5.6: gets a notice but works as expected, the returned value is the array without the values "two" and "three"
  • PHP 7: it only removes "two" and seems that the item with value "three" is not evaluated (see echo inside the loop, it doesn't print "three")

First loop => $key = 0, $value = "one" // continue

Second loop => $key = 1, $value = "second" // unset

Third loop => $key = 3, $value = "four" // WTF? where is the $key = 2, $value = "three"????

So I cannot understand what's going on. Our temporal solution is to iterate over the original object and unset from the copy. Does anybody knows which change in the PHP core (or ArrayObject/ArrayIterator) makes this? I have search about it but some people has this problem with foreach were the item iterated is by reference.

If you switch between PHP 5.6 and 7, the behaviour changes.

Example 1 (with iterator)

$elements = new ArrayObject();
$elements->append('one');
$elements->append('two');
$elements->append('three');
$elements->append('four');

print_r($elements);

$clone = clone $elements;
$it = $clone->getIterator();

echo "\n------\n";
foreach ($it as $key => $value) {
    echo $key."\t=>\t".$value."\n";
    if ($value == 'two' || $value == 'three') {
        $it->offsetUnset($key);
    }
}
echo "\n------\n";
print_r($clone);

Example 2 (without iterator)

$elements = new ArrayObject();
$elements->append('one');
$elements->append('two');
$elements->append('three');
$elements->append('four');

print_r($elements);

$clone = clone $elements;

echo "\n------\n";
foreach ($clone as $key => $value) {
    echo $key."\t=>\t".$value."\n";
    if ($value == 'two' || $value == 'three') {
        $clone->offsetUnset($key);
    }
}
echo "\n------\n";
print_r($clone);

Thanks so much!

like image 465
Daniel Nieto Avatar asked Oct 17 '16 13:10

Daniel Nieto


1 Answers

From my understanding , it is considered a bad practice to modify an array while looping through it, and the proper way to do it would be using array_filter.

Since you have an ArrayObject, one solution would be to export it to an array, filter it using array_filter and create a new ArrayObject from the filtered array.

See also here : Filter ArrayObject (PHP)

Probably this behavior is due to the fact that loops are handled differently in php7. As mentioned here: http://php.net/manual/en/control-structures.foreach.php in php5 foreach uses an internal array pointer in contrast to php7.

like image 85
orestiss Avatar answered Nov 12 '22 19:11

orestiss