A removable iterator is an iterator that implement the 'remove()' method.
This method remove the current item from the source data.
This kind of iterator is both usefull and dangerous and should be implemented with care.
Iterators musn't interfere with eachothers,
Modifying the source object mustn't break iterators usage,
I the source object is modified, only a reset() on the iterator should update the iterator behaviour.
To achieve these points, a remove() behavious should act with item value and not the item index. (index may change after the iterator instantiation).
Iterator which implements remove() should store an image of source data, snapshot of source at the very time the iterator was instantiated.
Reseting the iterator result in making a new image.
When source data is too ugge, remove() should be avoided.
When you can't ensure remove behaviour on more than one iterator avoid implementing it.
Example 2. MyArrayIterator improved
require_once "Types/Ref.php";
//
// Example of an array iterator implementation
//
// This second implementation explain how to use Ref class to allow a 'safe'
// remove() implementation.
//
class MyArrayIterator
{
var $_src;
var $_end = false;
var $_index = -1;
var $_value = null;
var $_values = array();
// constructor
//
// This constructor takes an array as first argument
//
function MyArrayIterator(&$array)
{
// store array reference
$this->_src =& $array;
// reset iterator to first position
$this->reset();
}
// reset iterator for next pass
//
function reset()
{
// unset value
// unset($this->_value);
// reset index
$this->_index = 0;
// initialize the source image for this iterator
$this->_values = array();
// copy array content using references
for ($i=0; $i<count($this->_src); $i++) {
$this->_values[] = ref($this->_src[$i]);
}
if (count($this->_values) == 0) {
// no data means end of iterator
$this->_end = true;
} else {
// otherwise, we reset _end flag
$this->_end = false;
// and we store the first item value (Ref object)
$this->_value = $this->_values[0];
}
}
// access next item
//
function &next()
{
// end already reached, do nothing
if ($this->_end) {
return;
}
$this->_index++;
// end reached
if ($this->_index >= count($this->_values)) {
$this->_end = true;
return;
}
// store current item value (Ref object)
$this->_value = $this->_values[$this->_index];
// return value
return $this->_value->obj;
}
// returns true if end not reached
//
function isValid()
{
return !$this->_end;
}
//
// getters
//
// retrieve current value by reference
//
// throws error if end reached
//
function &value()
{
if ($this->_end) {
return PEAR::raiseError("Iterator Out Of Bounds");
}
return $this->_value->obj;
}
// retrieve current item index
//
function index()
{
return $this->_index;
}
//
// optional implementation
//
// remove current item from source data
//
function remove()
{
if ($this->_end) {
return PEAR::raiseError("Iterator Out Of Bounds");
}
// we store objects and not only php types, otherwhise, we should have
// used array_search
for ($i=0; $i<count($this->_src); $i++) {
if ($this->_src[$i] === $this->_value->obj) {
// unset is somewhat problematic here, see below
unset($this->_src[$i]);
return;
}
}
}
}In this example, an image of source array is done on each iterator reset call.
The iterator does not use _src anymore but its own image. Thus, if the source data is modified by another iterator of by the programmer, the iterator is still conform to its knowledge of the data.
A reset, update the iterator image.
As Ref is used for storing references, we do not need unset or affectation by reference anymore.
Important :
As you guessed, these exemples mustn't be used in your programs, they are just presentation of Iterator implementations.
In the last exemple, the remove method act directly on the source array, this is a bad point as we can't use array_splice or array_values without loosing object and source array references. Unset will leave an empty slot in the source array.
The best way to iterate over php arrays is to use OArray object and its iterator instead of php arrays.