编程 PHP 8.6 的 SplFileObject:next() 与 fgets() 不再共享缓存,按行读文件要改写法

2026-11-08 21:04:15

PHP 8.6 的 SplFileObject:next() 与 fgets() 不再共享缓存,按行读文件要改写法

PHP 8.6 计划在 2026-11-05 发布 RC5,在 2026-11-19 发布 GA。这一版修改了 SplFileObject 的迭代缓存语义,同时弃用了它的 CSV 方法。直接受影响的是“按行读大文件”的 while 循环,以及手写 next() 跳行的代码。

相关文档:

next() 与 fgets() 原先共享的缓存已拆开

SplFileObject 是一个 Iterator 实现。foreach ($file as $line) 每轮先取 current(),再调 next(),这两个调用成对出现,所以 foreach 本身仍然可用。问题出在手写循环里把 next()、current()、fgets() 混着用的写法。

next() 的变化是:以前如果没有先调用 current() 缓存一行,next() 不会推进流,后续的 current() 拿到的还是上一行,重复行就是这么来的;从 8.6 起,next() 在没有缓存时直接推进流,后续 current() 返回新行。

fgets() 的变化方向相反:它不再把返回的行缓存给 current()。current() 改为从当前流位置重读,而不是返回 fgets() 刚给出的那一行。旧代码里:

$line = $file->fgets();
$same = $file->current(); // 8.5 及之前:与 $line 相同;8.6:已是下一行

也就是说,原来靠 current() 补位读到重复行的循环会变成漏行;原来靠 next() 不推进来反复读同一行的逻辑会直接失效。大文件扫描时少读一行往往不报错,只是统计或解析结果偏了,这类问题最难排查。

改法是一个循环只保留一个读取入口:要么 foreach,要么 while + fgets(),要么 current()/next() 成对出现。不要把 fgets() 的返回值再交给 current()。

EOF 之后的 key()

以前 next() 越过 EOF 后 key() 会无限自增;seek() 过 EOF 时,SplFileObject 与 SplTempFileObject 返回的 key() 还不一样,两者此前返回不同值。8.6 里 next() 过 EOF 不再增长 key(),seek() 过 EOF 则与 SplTempFileObject 对齐。

影响两处:一是自己维护行号、或在 EOF 之后继续调 next() 计数的代码;二是同时用过这两种对象、依赖其中某个返回值做比较的代码。行号本身仍从 0 起,foreach 里的 key 语义没变,变的是越界之后的行为。

DirectoryIterator 的返回类型

DirectoryIterator::key() 现在返回 int|string,current() 返回 string|SplFileInfo|static(具体形态取决于 FilesystemIterator 的 CURRENT_AS_* 标志)。给这两个方法加了窄类型声明,或写了强类型包装函数、子类的,需要放宽到联合类型,否则继承时签名不兼容。

CSV 方法弃用

同版本弃用的还有 SplFileObject::fgetcsv()、fputcsv()、setCsvControl()、getCsvControl(),改用流上的 fgetcsv()/fputcsv(),分隔符、包围符这类参数直接传给函数,不再通过 setCsvControl() 挂在对象上。另外 ArrayIterator 的 asort()、ksort()、uasort()、uksort()、natsort()、natcasesort()、getFlags()、setFlags()、serialize()、unserialize() 也一并弃用。

眼下可以做的:把混用 fgets() 与 current() 的循环挑出来,逐个改成单一读取路径;检查 EOF 之后还在继续 next() 的地方;给与 DirectoryIterator 相关的类型声明留出联合类型。

复制全文 生成海报 PHP 文件读取 升级踩坑

推荐文章

程序员茄子在线接单