10

(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!

Daniel Nieto
  • 131
  • 6
  • Please paste the actual code as text, not as a link or a picture. – RiggsFolly Oct 17 '16 at 13:21
  • 1
    ArrayObject use iterator in both cases, and if you unset offset then possition in iterator is over one and skip next element. If you do `$it->seek($key - 1);` after `$it->offsetUnset($key);` then it should work – Marek Janoud Oct 17 '16 at 13:26
  • Yes @MarekJanoud, it could work but it doesn't explain any about the change between php 5.6 and php 7 – Daniel Nieto Oct 17 '16 at 13:30
  • 7
    This is a known issue: https://bugs.php.net/bug.php?id=70246 Don't hold your horses on a fix, it's a tricky issue. – NikiC Oct 17 '16 at 13:59
  • Actually i'm not sure, and what i said `$it->seek($key - 1);` will not help, sorry, strange behaviour. Just follow answer and everything will be OK – Marek Janoud Oct 17 '16 at 14:01
  • fwiw, to delete keys by value, you could check [this answer](https://stackoverflow.com/questions/7225070/php-array-delete-by-value-not-key/11982622#11982622) – Ja͢ck Mar 10 '20 at 07:25

1 Answers1

1

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.

Community
  • 1
  • 1
orestiss
  • 2,183
  • 2
  • 19
  • 23
  • @orestiss could be a bad practice and could be a good suggestion but....the main problem is that in php 5.6 it works as expected and in php 7 no. Anything has changed in the core that makes this. I think that may be anything about next() (of ArrayIterator)... – Daniel Nieto Oct 17 '16 at 13:48
  • The question is fully answered in this answer if you read the last paragraph followed by its link containing the full explanation. Pointers are used so the array is not directly modified. You'll have to revert to PHP 5.6 or rewrite the loop differently as this is the newly expected behavior. – MaKR Oct 24 '16 at 06:20