如何在嵌套yield语句中迭代SplObjectStorage并保留外层迭代状态?
解决SplObjectStorage嵌套迭代时指针干扰的问题
问题本质
SplObjectStorage的迭代状态与存储实例绑定,不像数组的foreach会自动创建独立迭代上下文。嵌套迭代同一个SplObjectStorage实例时,内层迭代会将共享指针移至末尾,直接导致外层迭代终止,无法遍历后续元素。
可行解决方案
1. 获取独立迭代器(推荐,简洁高效)
SplObjectStorage实现了IteratorAggregate接口,调用getIterator()会返回独立的SplObjectStorageIterator实例,每个迭代器拥有专属指针状态,内层迭代不会干扰外层。
修改迭代函数示例:
function iterate2(\SplObjectStorage $objects) { // 使用独立迭代器而非直接遍历原存储 $iterator = $objects->getIterator(); foreach ($iterator as $object) { yield ((array)$object)[0]; } }
或直接在遍历逻辑中使用:
foreach ($objects->getIterator() as $a) { foreach ($objects->getIterator() as $b) { $valA = ((array)$a)[0]; $valB = ((array)$b)[0]; echo "$valA -> $valB\n"; } }
该方法无需克隆存储,性能开销极低,彻底避免指针干扰。
2. 克隆存储进行迭代(适合需隔离原存储的场景)
若迭代过程中可能修改原存储,或需要完全隔离迭代上下文,可在生成器内部克隆SplObjectStorage:
function findAll(\SplObjectStorage $objects) { $clone = clone $objects; foreach ($clone as $object) { yield $object; } }
克隆开销在多数场景下可忽略,同时能保证原存储的迭代状态不受影响。
3. 手动记录并恢复指针(不推荐,仅特殊场景使用)
若受限制无法使用前两种方法,可在外层迭代时记录当前key,内层迭代结束后重置指针位置。但该方法依赖存储key的稳定性,且迭代中修改存储会导致失效,代码冗余:
foreach ($objects as $outerKey => $a) { $valA = ((array)$a)[0]; // 内层迭代 foreach ($objects as $b) { $valB = ((array)$b)[0]; echo "$valA -> $valB\n"; } // 重置外层指针到之前的位置 $objects->rewind(); while ($objects->key() !== $outerKey) { $objects->next(); } // 移动指针到下一个元素,准备外层下一次迭代 $objects->next(); }
为什么数组可以正常嵌套迭代?
PHP数组的foreach会自动创建迭代快照(独立指针上下文),每次遍历基于快照进行,内层迭代不会修改外层的迭代状态。而SplObjectStorage默认的foreach直接使用自身迭代器状态,没有自动创建独立上下文的机制。
内容的提问来源于stack exchange,提问作者Javad
相关产品推荐
相关产品推荐

