How to access first element in iterator?

There are only two class interfaces that can be traversed: Iterator and IteratorAggregate (any other must implement one of them).


Iterator

First element of Iterator can be obtained as follows:

$iterator->rewind();
if (!$iterator->valid()) {
    throw new Exception('There is no any element!');
}
$firstElement = $iterator->current();

If you are sure:

  • the $iterator was never been traversed by foreach, or if it was but the loop was never stopped with break or return (since PHP 7 this point is irrelevant because foreach does not affect pointer)
  • there was never called $iterator->next();

you can omit the $iterator->rewind(); from the previous example.

If you are sure the count of elements in $iterator is not zero, you can even omit the condition block testing $iterator->valid().

So if these previous conditions are preserved then what you need is just:

$firstElement = $iterator->current();

IteratorAggregate

IteratorAggergate is actually just an envelope for an Iterator or another IteratorAggregate. Logically that means there is an Iterator at the end.

If you know how many levels deep the Iterator is, just grab it and use as in the very first example:

$iterator = $iteratorAggregate->getIterator();

But if you don't know the deepness, you may use a solution which works also for an Iterator:

$array = iterator_to_array($iteratorAggregate);
// $array is an ordinary array now
if (count($array) === 0) {
    throw new Exception('There is no any element!');
}
$firstElement = reset($array);

Unfortunately in case of biig array this is a little overkill because copy of all elements must be created despite we need just one. Besides if the Iterator is an infinite Generator you will run out of memory.

There is one solution that works:

while ($iterator instanceof \IteratorAggregate) {
    $iterator = $iterator->getIterator();
}
// $iterator now contains instance of Iterator

I made a simple benchmark for an array of 10000 members and this solution was almost 6 times faster.

So, the universal solution which will work for all cases is:

while ($iterator instanceof \IteratorAggregate) {
    $iterator = $iterator->getIterator();
}
$iterator->rewind();
if (!$iterator->valid()) {
    throw new Exception('There is no any element!');
}
$firstElement = $iterator->current();

LLAP


Assuming by "iterable", you mean that the object implements the Iterator interface, you can use $obj->current() to retrieve the current element, so $obj->current()->propName is probably what you want.

If the iterator pointer has been moved (for example, if it was used in a foreach, which doesn't reset the pointer), then you can call $obj->rewind() to set the pointer back to the first element before you call $obj->current().


For any types of iterables(array, Iterator, IteratorAggregate) you can use this function:

/* public static */ function first(iterable $iterable, $default = null) {
    foreach ($iterable as $item){
        return $item;
    }
    return $default;
}

If you prefer to throw an exception if iterable is empty, you can use that version:

/* public static */ function firstOrThrow(iterable $iterable, \Throwable $e) {
    foreach ($iterable as $item){
        return $item;
    }
    throw $e;
}

That works both for arrays and iterator objects and also computes only first item in iterators which can improve performance is some cases.

Tags:

Php

Iterator