必须完整实现Iterator接口全部五个方法,rewind()需重置指针,valid()应基于位置而非isset判断,推荐优先使用ArrayIterator或Generator替代手写。

PHP 的 Iterator 接口怎么写才不崩
直接实现 Iterator 接口容易出错,不是漏方法就是返回类型不对,最常见是 current() 返回 null 却没处理好边界,导致 foreach 报 Fatal error: Uncaught TypeError。
必须完整实现五个方法:current()、key()、next()、rewind()、valid()。其中 rewind() 不能只写空逻辑——它得真正重置内部指针,否则第二次遍历就失效。
实操建议:
-
rewind()里要显式重置索引变量(比如$this->position = 0),别依赖构造函数初始值 -
valid()别用isset($this->data[$this->position])——如果数组键不连续会误判,改用$this->position data) - 所有方法都该有类型声明,尤其
current(): mixed(PHP 8.0+)或具体类型,避免隐式转换引发奇怪行为
用 ArrayIterator 或 RecursiveArrayIterator 处理嵌套数组
手写迭代器太重,90% 的嵌套遍历需求其实用现成的 SPL 迭代器就够了。但要注意:ArrayIterator 只扁平一层,遇到多维数组会把子数组当单个元素;真要递归展开,得换 RecursiveArrayIterator + RecursiveIteratorIterator 组合。
立即学习“PHP免费学习笔记(深入)”;
实操建议:
- 想展开所有层级的键值对?用
new RecursiveIteratorIterator(new RecursiveArrayIterator($data)),但默认只展到叶子节点,中间层键名会被丢掉 - 需要保留路径(比如
users.0.name)?得配合getInnerIterator()->key()手动拼接,不能指望它自动带路径 -
RecursiveIteratorIterator的第二个参数控制遍历模式:RecursiveIteratorIterator::SELF_FIRST先输出父再子,::LEAVES_ONLY(默认)只吐叶子,选错会导致结果为空
foreach 里修改集合时 Iterator 失效的坑
在 foreach 中调用 append()、offsetSet() 或直接改 $this->data,很可能让当前迭代器状态错乱——next() 跳过元素、valid() 提前返回 false、甚至无限循环。
这不是 PHP Bug,是迭代器契约决定的:SPL 迭代器默认不保证「内部结构变更时的稳定性」。
实操建议:
- 绝对不要在 foreach 循环体里增删集合元素;如需构建新集合,先收集 key/val,循环结束后批量操作
- 若必须边遍历边改,改用
for+count()控制,靠索引访问,避开迭代器状态 - 自定义迭代器中,可在
next()开头加校验:if ($this->position >= count($this->data)) { $this->position = count($this->data); },防止越界后继续调用
性能敏感场景下,Generator 比手写 Iterator 更轻量
当你只需要「顺序吐数据」,不关心随机访问或倒序,yield 写的生成器比完整实现 Iterator 接口快 2–3 倍,内存占用也低得多——因为没对象实例开销,也不用维护一堆状态变量。
但生成器无法 rewind(除非用 IteratorIterator 包一层),也不能多次遍历,这点和标准迭代器不同。
实操建议:
- 读大文件逐行处理?用
function readLines(string $path): \Generator { $f = fopen($path, 'r'); while (($line = fgets($f)) !== false) yield $line; fclose($f); } - 要支持多次遍历?别硬扛,直接封装成类,内部用
yield from调用生成器,每次rewind()重新创建生成器实例 - 别在生成器里做耗时操作(比如 DB 查询),yield 是协程点,阻塞会拖慢整个循环



















