Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ArrayObject doesn't allow me to unset a value, while iterating over it

I've got this notice:

ArrayIterator::next(): Array was modified outside object and internal position is no longer valid in /var/www...

which is produced by this code, at the begining of the foreach loop. Together with the notice, the foreach loop starts iterating all over again. In other words, the internal position is reset whenever this thing happens. But acording to php manual, ArrayObject is using ArrayIterator by default.

And manual says this about ArrayIterator

This iterator allows to unset and modify values and keys while iterating over Arrays and Objects.

Am I missing something here? I found some bugreports about ArratIterator, but not this kind. Is it a bug or is it my bad?

version: PHP Version 5.3.10-1ubuntu3.4

<?php
//file 1:
// no namespace
abstract class holder extends \ArrayObject{
    // abstract function init();

    public function __construct($init){
        parent::__construct($init, 1);
    }
}?>

<?php
//file 2:
namespace troops;
class holder extends \holder{
    public function __construct(){
        parent::__construct($this->init());
    }

    private function init(){
        return array( /*... some data from db ...*/ );
    }

    public function saveData(){
        foreach($this as $k  => $v){
            $this->save($v);
            if($v->number_of_items==0) {
                unset($k);
                // $this->offsetUnset($k); // tryed both 
            }
        }
    }
}
?>
like image 628
enrey Avatar asked Jan 14 '23 14:01

enrey


2 Answers

ArrayObject implements IteratorAggregate which means it has a method called getIterator() which returns an iterator.

php's foreach loop will automatically retrieve an iterator by calling the getIterator() method for you to retrieve an iterator to iterate over. This is convenient, but you need to get a reference to this iterator in order to call the offsetUnset() method on the iterator itself. The key thing here is you must call the iterators offsetUnset() method, not the ArrayObjects offsetUnset() method.

$ao = new ArrayObject();
$ao[] = 9;
$iter = $ao->getIterator();

foreach ($iter as $k => $v) 
    $iter->offsetUnset($k); // no error

The underlying ArrayObject that the iterator is iterating over will be mutated, so if you have more than one active iterator at the same time over the same Arrayobject, you'll still encounter the same error.

The rationale for this is likely so that the iterators can be memory efficient and not have to copy the underlying ArrayObject, because a copy is the only simple solution to dealing with the complexity of deciding what the current iterator position should be when things are added to or deleted from the underlying array.

like image 102
goat Avatar answered Jan 28 '23 14:01

goat


In case you remove the last record from array than the next foreach loop can fail (internaly calling $this->next() ) Forexample when i have array of one item and I will unset it, than next foreach fails.

So it helped me to test the validy and break in this case the next loop.

deleteOffset = true;

foreach ($iterator as $key => $value)
    {
            if ($deleteOffset && $iterator->offsetExists($key) ) 
            {
                 $iterator->offsetUnset($key);                                       
            }                

        //if remove last record than the foreach ( $this->next() ) fails so 
        //we have to break in this case the next ->next call
        //after any ->offsetUnset calls

        if (!$iterator->valid()) break;

    }
like image 28
Mike Rehacek Avatar answered Jan 28 '23 14:01

Mike Rehacek