The function you're calling could use static variables to remember its state from one call to the next; however you'd only be able to use it for one loop at a time.
An alternative would be to write a class, and instantiate an object from it that will keep track of where it is in the collection being iterated over (and provide methods for doing the iteration). In fact PHP provides a variety of such iterators in its [man]SPL[/man].
For a one-off enumeration (much like mysql_fetch_array, mysql_reset notwithstanding) I whacked this interface together. A long time ago, so it's probably rubbish by now, and obviously it's not going to be customised to your wants. Look on it as an example.
/**
* A simple enumeration interface, for when the internal Iterator interface is
* too high-powered (e.g., when a single traverse is sufficient and you aren't
* interested in retaining the elements for potential future traversals). Yes,
* it's laziness (you only need to implement two methods instead of five - and
* some of those five can be quite unfriendly if all you want is the once-only
* traverse you get with an Enumeration).
*
* For equipping an existing enumeration with the Iterator interface, or vice
* versa, see the EnumerationIterator and IteratorEnumeration classes below).
*/
interface Enumeration
{
// Boolean function; return true iff there are elements in the enumeration that
// have not yet been retrieved.
public function hasMoreElements();
// Retrieve and return the next element in the enumeration. Throw an
// OutOfBoundsException if there are no elements left.
public function nextElement();
}
/**
* An enumeration that is always empty. This can be used to simplify the
* handling of requests for enumerations of things that may not contain anything to
* enumerate. If you're asked for an enumeration and have nothing to enumerate, you
* can return this instead of null or false and save the requester from having to
* special-case your response.
*
* Since all EmptyEnumerations are identical and possess no state, this class
* provides a prototypical instance to save creating new ones. To use the
* prototype, replace
* new EmptyEnumeration;
* with
* EmptyEnumeration::instance();
*/
final class EmptyEnumeration implements Enumeration
{
static private $instance;
public function hasMoreElements()
{
return false;
}
public function nextElement()
{
throw new OutOfBoundsException('No more elements');
}
// Instantiate the prototype.
static public function static__construct()
{
static $constructed = false;
if($constructed) return;
$constructed = true;
self::$instance = new self;
}
public function instance()
{
return self::$instance;
}
}
EmptyEnumeration::static__construct();
/**
* A wrapper that allows any single entity to be handled as an enumeration.
*
* Again, it saves special-casing. In this instance, you're expected to return
* an enumeration, but you only have a single item. Fine: you can wrap it up
* in one of these and return it that way.
*/
final class OneElementEnumeration implements Enumeration
{
private $thing;
private $isDone;
public function __construct($element)
{
$this->isDone = false;
$this->thing = $element;
}
public function hasMoreElements()
{
return !$this->isDone;
}
public function nextElement()
{
if($this->isDone)
throw new OutOfBoundsException('No more elements');
$this->isDone = true;
$the_thing = $this->thing;
$this->thing = null; // We never use it again, so kill the ref
return $the_thing;
}
}
/**
* A wrapper that allows an array to be traversed as an enumeration. (Kind of a
* distaff dual to the EnumerationIterator that allows an enumeration to be
* traversed like an array. But only distaff: a _real_ dual to this class would be
* called EnumerationArray. Exercise for the reader.)
*/
final class ArrayEnumeration implements Enumeration
{
private $array;
public function __construct($array)
{
if(!is_array($array))
throw new InvalidArgumentException("Constructor requires an array");
$this->array = $array;
}
public function hasMoreElements()
{
return count($this->array)>0;
}
public function nextElement()
{
if(!count($this->array))
throw new OutOfBoundsException('No more elements');
return array_shift($this->array);
}
}
/**
* Wrapper class to equip an enumeration with an Iterator interface, thus
* allowing one to iterate over an enumeration's elements with foreach().
*
* Since this class retains a memory of the Enumeration's elements, it can be
* iterated over multiple times.
*/
final class EnumerationIterator implements Iterator
{
private $memory = array();
private $walker = 0;
private $enumeration;
public function __construct(Enumeration $enumeration)
{
$this->enumeration = $enumeration;
}
// Contractual obligations to the Iterator interface
public function current()
{
if(!$this->valid()) throw new OutOfBoundsException('Ran off the end of the Iterator');
return $this->memory[$this->walker];
}
public function key()
{
return $this->walker;
}
public function next()
{
++$this->walker;
}
public function rewind()
{
$this->memorywalker = 0;
}
public function valid()
{
if($this->walker<count($this->memory)) return true;
if(!$this->enumeration->hasMoreElements()) return false;
// Feels weird to do it here, but it keeps all the checking and legwork
// in one place.
$this->memory[$this->walker] = $this->enumeration->nextElement();
return true;
}
}
/**
* Wrapper class to allow an SPL Iterator to look like an Enumeration
*
* A direct dual to EnumerationIterator
*/
final class IteratorEnumeration implements Enumeration
{
private $iterator;
public function __construct(Iterator $iterator)
{
$this->iterator = $iterator;
$this->iterator->rewind();
}
public function hasMoreElements()
{
return $this->iterator->valid();
}
public function nextElement()
{
if(!$this->hasMoreElements())
throw new OutOfBoundException('No more elements');
$this->iterator->next();
return $this->iterator->current();
}
}