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 时,不能通过引用修改元素值(与数组不同)。
最佳实践
- 使用 Generator 替代简单 Iterator:如果遍历逻辑简单且不需要 rewind,优先使用 Generator。
- 实现 Countable:如果迭代器有明确的元素数量,同时实现
Countable接口。 - 封装复杂逻辑:将复杂的遍历逻辑封装在 Iterator 实现中,对外提供简洁的
foreach接口。 - 正确实现 rewind:
rewind()应该将迭代器重置到初始状态。
php
<?php
declare(strict_types=1);
// 推荐模式:简单遍历用 Generator
function simpleRange(int $start, int $end): \Generator
{
for ($i = $start; $i <= $end; $i++) {
yield $i;
}
}