Skip to content
Lesson 9 of 9

Step 1 of 6 · Reading · ~3 min

Learn

Iteration Protocols

Implement these interfaces and your custom objects gain foreach / [] syntax — the same as built-in arrays.

Iterator interface

Five methods. Implement them and foreach knows how to walk your object:

class Range implements \Iterator {
    private int $pos;
    
    private int $start;
    private int $end;

    public function __construct(int $start, int $end) {
        $this->start = $start;
        $this->end   = $end;
        $this->pos   = $start;
    }

    public function current()          { return $this->pos; }
    public function key()              { return $this->pos - $this->start; }
    public function next(): void       { $this->pos++; }
    public function rewind(): void     { $this->pos = $this->start; }
    public function valid(): bool      { return $this->pos < $this->end; }
}

foreach (new Range(5, 10) as $key => $value) {
    echo "$key => $value\n";
}
// 0 => 5
// 1 => 6 ... 4 => 9

The protocol:

  1. rewind() — reset to the beginning
  2. valid() — is there a current element?
  3. current() / key() — the element and its key
  4. next() — advance
  5. back to step 2

In practice, use \Generator (the previous lesson) — it gives you an Iterator with one yielded value per iteration.

IteratorAggregate — the easy way

For most cases, implement the simpler IteratorAggregate:

class UserCollection implements \IteratorAggregate {
    private array $users;
    public function __construct(array $users) { $this->users = $users; }

    public function getIterator(): \Generator {
        foreach ($this->users as $u) yield $u;
    }
}

Return anything that's already iterable — including a generator. Much less code than the 5-method Iterator protocol.

ArrayAccess — bracket syntax

Make $obj[$key] work:

class Config implements \ArrayAccess {
    private array $data;

    public function __construct(array $data = []) { $this->data = $data; }

    public function offsetExists($key): bool {
        return isset($this->data[$key]);
    }
    public function offsetGet($key) {
        return $this->data[$key] ?? null;
    }
    public function offsetSet($key, $value): void {
        if ($key === null) $this->data[] = $value;
        else $this->data[$key] = $value;
    }
    public function offsetUnset($key): void {
        unset($this->data[$key]);
    }
}

$c = new Config(['theme' => 'dark']);
echo $c['theme'];        // dark — offsetGet
$c['lang'] = 'en';        // offsetSet
isset($c['theme']);       // offsetExists
unset($c['lang']);        // offsetUnset

Countable — count() support

class Cart implements \Countable {
    private array $items;
    public function __construct(array $items = []) { $this->items = $items; }

    public function count(): int { return count($this->items); }
}

echo count(new Cart(['a', 'b', 'c']));   // 3

Combining them

A real collection class often implements all three: IteratorAggregate, ArrayAccess, Countable. Then it walks like an array, indexes like an array, counts like an array.

Common mistakes

  • Writing PHP 8 signaturesmixed as a parameter or return type, and constructor property promotion, are both PHP 8. On this course's 7.4 grader promotion is a parse error and : mixed is read as a class name, so declare the property and leave the type off.
  • Implementing Iterator from scratch when IteratorAggregate + a generator would be simpler.
  • Forgetting rewind() must reset state — foreach rewinds at the start. If state isn't reset, the second foreach sees nothing.
  • ArrayAccess offsetSet receiving null key — that's $obj[] = $value — append. Handle it.
  • Mutating during iteration — same gotcha as arrays. Snapshot if you need to mutate.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…