Skip to content

Iterator 接口

概述

Iterator 是 PHP SPL(Standard PHP Library)中定义的核心接口,用于实现自定义的对象遍历行为。实现了 Iterator 接口的类可以在 foreach 循环中使用,并支持完整的迭代控制。本章将详细讲解 Iterator 接口的 5 个方法、foreach 支持机制,以及各种实现模式。

接口定义

Iterator 扩展自 Traversable,定义了 current()key()next()rewind()valid() 五个方法。

基础概念

Iterator 接口方法

  • current() — 返回当前元素的值
  • key() — 返回当前元素的键
  • next() — 将指针移动到下一个元素
  • rewind() — 将指针重置到第一个元素
  • valid() — 检查当前位置是否有效

foreach 支持

PHP 的 foreach 语句支持任何实现了 Traversable 接口的对象,包括 Iterator

遍历顺序

Iterator 的遍历顺序由实现者完全控制。默认是从前到后,但也可以实现双向遍历或其他自定义顺序。

语法与代码

基本 Iterator 实现

php
<?php
declare(strict_types=1);

class NumberIterator implements \Iterator
{
    private int $position = 0;
    private array $numbers;

    public function __construct(array $numbers)
    {
        $this->numbers = array_values($numbers);
    }

    public function current(): int
    {
        return $this->numbers[$this->position];
    }

    public function key(): int
    {
        return $this->position;
    }

    public function next(): void
    {
        $this->position++;
    }

    public function rewind(): void
    {
        $this->position = 0;
    }

    public function valid(): bool
    {
        return isset($this->numbers[$this->position]);
    }
}

$iterator = new NumberIterator([10, 20, 30, 40, 50]);

foreach ($iterator as $key => $value) {
    echo "{$key}: {$value}\n";
}
// 0: 10
// 1: 20
// 2: 30
// 3: 40
// 4: 50

步长迭代器

php
<?php
declare(strict_types=1);

class StepIterator implements \Iterator
{
    private int $current;
    private int $step;
    private int $max;
    private int $position = 0;

    public function __construct(int $start, int $step, int $max)
    {
        $this->current = $start;
        $this->step = $step;
        $this->max = $max;
    }

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

    public function key(): int
    {
        return $this->position;
    }

    public function next(): void
    {
        $this->current += $this->step;
        $this->position++;
    }

    public function rewind(): void
    {
        $this->current = $this->current - ($this->step * $this->position);
        $this->position = 0;
    }

    public function valid(): bool
    {
        return $this->current <= $this->max;
    }
}

foreach (new StepIterator(0, 5, 20) as $key => $value) {
    echo "{$value} ";
}
// 0 5 10 15 20

手动迭代控制

php
<?php
declare(strict_types=1);

$iterator = new NumberIterator(['a', 'b', 'c']);

// 手动调用迭代器方法
$iterator->rewind();
while ($iterator->valid()) {
    echo "key={$iterator->key()}, value={$iterator->current()}\n";
    $iterator->next();
}

// 重新开始
$iterator->rewind();
echo "重置后: key={$iterator->key()}, value={$iterator->current()}\n";
// 重置后: key=0, value=a

带过滤功能的 Iterator

php
<?php
declare(strict_types=1);

class EvenNumberIterator implements \Iterator
{
    private array $numbers;
    private int $position = 0;
    /** @var array<int, int> */
    private array $filtered = [];

    public function __construct(array $numbers)
    {
        $this->numbers = array_values($numbers);
        $this->filtered = array_values(
            array_filter($this->numbers, fn(int $n): bool => $n % 2 === 0)
        );
    }

    public function current(): int
    {
        return $this->filtered[$this->position];
    }

    public function key(): int
    {
        return $this->filtered[$this->position];
    }

    public function next(): void
    {
        $this->position++;
    }

    public function rewind(): void
    {
        $this->position = 0;
    }

    public function valid(): bool
    {
        return isset($this->filtered[$this->position]);
    }
}

foreach (new EvenNumberIterator([1, 2, 3, 4, 5, 6, 7, 8]) as $key => $value) {
    echo "{$value} ";
}
// 2 4 6 8

详细说明

foreach 的调用顺序

foreach ($iterator as $key => $value) {
    // 循环体
}

等价于:

$iterator->rewind();
while ($iterator->valid()) {
    $key = $iterator->key();
    $value = $iterator->current();
    // 循环体
    $iterator->next();
}

Iterator 与 Generator 的对比

当遍历逻辑简单时,Generator 通常更简洁。但 Iterator 提供了更强的控制力(如 rewind、多次遍历)。

Countable 接口配合

php
<?php
declare(strict_types=1);

class CountableIterator implements \Iterator, \Countable
{
    private array $items;
    private int $position = 0;

    public function __construct(array $items)
    {
        $this->items = array_values($items);
    }

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

    public function current(): mixed { return $this->items[$this->position]; }
    public function key(): int { return $this->position; }
    public function next(): void { $this->position++; }
    public function rewind(): void { $this->position = 0; }
    public function valid(): bool { return isset($this->items[$this->position]); }
}

$iterator = new CountableIterator([1, 2, 3]);
echo "总数: " . count($iterator) . "\n"; // 总数: 3

实战示例

实战:分页迭代器

php
<?php
declare(strict_types=1);

class PaginatedIterator implements \Iterator
{
    private int $totalItems;
    private int $pageSize;
    private int $currentPage;
    private int $position = 0;
    private ?array $currentPageData = null;

    public function __construct(
        private readonly callable $fetchPage,
        int $totalItems,
        int $pageSize = 20
    ) {
        $this->totalItems = $totalItems;
        $this->pageSize = $pageSize;
        $this->currentPage = 0;
        $this->loadPage();
    }

    private function loadPage(): void
    {
        $this->currentPageData = ($this->fetchPage)($this->currentPage, $this->pageSize);
    }

    public function current(): mixed
    {
        $offset = $this->position % $this->pageSize;
        return $this->currentPageData[$offset] ?? null;
    }

    public function key(): int
    {
        return $this->position;
    }

    public function next(): void
    {
        $this->position++;
        if ($this->position % $this->pageSize === 0 && $this->position < $this->totalItems) {
            $this->currentPage++;
            $this->loadPage();
        }
    }

    public function rewind(): void
    {
        $this->position = 0;
        $this->currentPage = 0;
        $this->loadPage();
    }

    public function valid(): bool
    {
        return $this->position < $this->totalItems;
    }
}

实战:目录迭代器

php
<?php
declare(strict_types=1);

class DirectoryIteratorCustom implements \Iterator
{
    /** @var \SplFileInfo[] */
    private array $files = [];
    private int $position = 0;

    public function __construct(string $path, string $pattern = '*')
    {
        $files = glob("{$path}/{$pattern}");
        if ($files !== false) {
            foreach ($files as $file) {
                $this->files[] = new \SplFileInfo($file);
            }
        }
    }

    public function current(): \SplFileInfo
    {
        return $this->files[$this->position];
    }

    public function key(): string
    {
        return $this->files[$this->position]->getPathname();
    }

    public function next(): void
    {
        $this->position++;
    }

    public function rewind(): void
    {
        $this->position = 0;
    }

    public function valid(): bool
    {
        return isset($this->files[$this->position]);
    }
}

注意事项

Iterator 与内部指针

Iterator 接口不使用 PHP 数组的内部指针。实现了 Iterator 接口的对象通过自定义方法管理遍历状态。

foreach 中的引用

foreach 遍历 Iterator 时,不能通过引用修改元素值(与数组不同)。

最佳实践

  1. 使用 Generator 替代简单 Iterator:如果遍历逻辑简单且不需要 rewind,优先使用 Generator。
  2. 实现 Countable:如果迭代器有明确的元素数量,同时实现 Countable 接口。
  3. 封装复杂逻辑:将复杂的遍历逻辑封装在 Iterator 实现中,对外提供简洁的 foreach 接口。
  4. 正确实现 rewindrewind() 应该将迭代器重置到初始状态。
php
<?php
declare(strict_types=1);

// 推荐模式:简单遍历用 Generator
function simpleRange(int $start, int $end): \Generator
{
    for ($i = $start; $i <= $end; $i++) {
        yield $i;
    }
}

参考链接