iterator_apply

Calls a callback for every element of a Traversable and returns how many iterations ran.

Signature
iterator_apply(Traversable $iterator, callable $callback, ?array $args = null): int
  • Added in PHP 5.1.0

Parameters

ParameterTypeDescription
$iteratorTraversableThe iterator, IteratorAggregate or generator to iterate over. Plain arrays are not accepted.
$callbackcallableCalled once per element. It receives only the values in args — never the current element — and must return true for iteration to continue.
$argsoptional?array= nullArray of values passed to the callback as separate positional arguments. With null the callback is called with no arguments at all.

Return value

intThe number of iterations performed. If the callback stops the loop by returning a falsy value, that final iteration is still counted.

Errors

Throws a TypeError if $iterator is not a Traversable or $callback is not a valid callable; any exception thrown inside the callback propagates out and aborts the iteration.

Changelog

  • PHP 8.0Passing a value that is not Traversable, or a callback that is not callable, now throws a TypeError; earlier versions emitted a warning and returned null.

iterator_apply runs a callback once per element of any Traversable — an Iterator, an IteratorAggregate or a generator — and returns the number of iterations it performed. It rewinds the iterator first and advances it for you, so the callback never calls next() itself.

The design surprises almost everyone: the callback is not handed the current element. It receives exactly the values you put in the third argument, in order, and nothing else; with no third argument it is called with zero arguments. The idiomatic fix is to pass the iterator itself so the callback can read current() and key(). Pass the object that is actually being driven — for an IteratorAggregate such as ArrayObject, iterator_apply pulls a fresh iterator from getIterator(), so a separately obtained iterator you hand to the callback stays parked on the first element.

The second surprise is the return contract: the callback must return true to continue. A callback with no return yields null, which is falsy, so the loop stops after one element. That does make early exit easy — return false and iteration stops, and the returned count includes the iteration that stopped it.

When to reach for it

Use it when the callback and its arguments are configured elsewhere and you want the iteration count back for free. For ordinary looping a foreach over the same Traversable is shorter, faster and easier to read.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Walk an Iterator and stop as soon as a running total crosses a limit, getting back how many elements were consumed.
  • Count how many rows a generator actually produced while logging each one, in a single call.
  • Drive any Traversable with a handler callback and its extra arguments that were assembled elsewhere in a pipeline.

Frequently asked questions

Why doesn't my iterator_apply callback receive the current element?

Because it never does — the callback only receives the values in the third argument, so with no third argument it is called with no parameters. Pass the iterator itself, as in [$it], and read $it->current() inside the callback.

What is the difference between iterator_apply and foreach?

foreach hands you the key and value directly and is shorter and faster, so it is the better default. iterator_apply is worth it when the callback and its arguments are built elsewhere, or when you want the iteration count returned.

Can iterator_apply be used on an array?

No — the first parameter is typed Traversable, so passing an array throws a TypeError. Wrap the array in an ArrayIterator, or use array_walk or a foreach instead.

More examples

Stopping early and reading the count

Prints the first three pairs (0 => 4, 1 => 8, 2 => 15) and then "stopped after 3 iterations". The element that returned false is still counted, so the return value is the number of times the callback ran, not the number of successful elements.

Example · php
<?php
$numbers = new ArrayIterator([4, 8, 15, 16, 23, 42]);

$seen = iterator_apply($numbers, function (Iterator $it): bool {
    echo $it->key(), ' => ', $it->current(), "\n";
    return $it->current() < 15; // false stops the loop
}, [$numbers]);

echo "stopped after $seen iterations\n";

A nullary callback over a generator

Outputs "callback ran 5 times, iterator_apply returned 5". With the third argument omitted the callback takes no parameters at all — it cannot see the yielded values, so this shape is only useful for counting or for side effects that do not depend on the element.

Example · php
<?php
function evens(int $max): Generator {
    for ($i = 2; $i <= $max; $i += 2) {
        yield $i;
    }
}

$hits = 0;
$total = iterator_apply(evens(10), function () use (&$hits): bool {
    $hits++;
    return true;
});

echo "callback ran $hits times, iterator_apply returned $total\n";

IteratorAggregate: pass the inner iterator

Prints "x=1 y=2 z=3" and then "total: 3". Passing $data as the first argument instead would still be accepted, but iterator_apply would advance a fresh iterator of its own while $inner stayed on the first entry, printing "x=1" three times.

Example · php
<?php
$data  = new ArrayObject(['x' => 1, 'y' => 2, 'z' => 3]);
$inner = $data->getIterator(); // ArrayObject is not an Iterator itself

$n = iterator_apply($inner, function (Iterator $it): bool {
    echo $it->key(), '=', $it->current(), ' ';
    return true;
}, [$inner]);

echo "\ntotal: $n\n";

Discussion

  • Be the first to comment on this lesson.