IteratorAggregate
概述
IteratorAggregate 是 PHP SPL 中定义的接口,用于创建"外部迭代器"模式的集合类。与直接实现 Iterator 接口不同,IteratorAggregate 只需要实现一个 getIterator() 方法,返回一个 Traversable 对象。这种方式将迭代逻辑委托给外部的迭代器,使集合类本身更简洁。
核心区别
Iterator:类自己管理迭代状态(5 个方法)IteratorAggregate:类委托给外部迭代器(1 个方法)
基础概念
getIterator() 方法
getIterator() 是 IteratorAggregate 接口的唯一方法,返回一个 Traversable 对象(通常是 Iterator 或 Generator)。
外部迭代器模式
集合类本身不直接实现迭代逻辑,而是创建并返回一个专门的迭代器对象。这种方式实现了关注点分离。
与 Iterator 对比
IteratorAggregate 更适合"集合"类型的类,而 Iterator 更适合需要精细控制遍历过程的场景。
语法与代码
基本 IteratorAggregate 实现
php
<?php
declare(strict_types=1);
class BookCollection implements \IteratorAggregate
{
/** @var array<int, string> */
private array $books = [];
public function add(string $title): void
{
$this->books[] = $title;
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->books);
}
}
$collection = new BookCollection();
$collection->add('PHP 权威指南');
$collection->add('设计模式');
$collection->add('Clean Code');
foreach ($collection as $key => $title) {
echo "{$key}: {$title}\n";
}
// 0: PHP 权威指南
// 1: 设计模式
// 2: Clean Code使用 Generator 作为迭代器
php
<?php
declare(strict_types=1);
class FilteredCollection implements \IteratorAggregate
{
/** @var array<string, int> */
private array $data;
public function __construct(array $data)
{
$this->data = $data;
}
public function getIterator(): \Traversable
{
foreach ($this->data as $key => $value) {
if ($value > 10) {
yield $key => $value;
}
}
}
}
$collection = new FilteredCollection(['a' => 5, 'b' => 15, 'c' => 3, 'd' => 25]);
foreach ($collection as $key => $value) {
echo "{$key}: {$value}\n";
}
// b: 15
// d: 25自定义迭代器配合 IteratorAggregate
php
<?php
declare(strict_types=1);
class ReverseArrayIterator implements \Iterator
{
private array $items;
private int $position;
public function __construct(array $items)
{
$this->items = array_values($items);
$this->position = count($this->items) - 1;
}
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 = count($this->items) - 1; }
public function valid(): bool { return $this->position >= 0; }
}
class ReverseCollection implements \IteratorAggregate
{
public function __construct(
private readonly array $items
) {}
public function getIterator(): \Traversable
{
return new ReverseArrayIterator($this->items);
}
}
$collection = new ReverseCollection([1, 2, 3, 4, 5]);
foreach ($collection as $key => $value) {
echo "{$value} ";
}
// 5 4 3 2 1多种迭代方式
php
<?php
declare(strict_types=1);
class Team implements \IteratorAggregate
{
/** @var array<int, array{name: string, role: string}> */
private array $members = [];
public function add(string $name, string $role): void
{
$this->members[] = ['name' => $name, 'role' => $role];
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->members);
}
public function getNames(): \Generator
{
foreach ($this->members as $member) {
yield $member['name'];
}
}
public function getByRole(string $role): \Generator
{
foreach ($this->members as $member) {
if ($member['role'] === $role) {
yield $member;
}
}
}
}
$team = new Team();
$team->add('Alice', 'developer');
$team->add('Bob', 'designer');
$team->add('Charlie', 'developer');
echo "所有成员:\n";
foreach ($team as $member) {
echo " {$member['name']} ({$member['role']})\n";
}
echo "开发人员:\n";
foreach ($team->getByRole('developer') as $dev) {
echo " {$dev['name']}\n";
}详细说明
IteratorAggregate 的优势
- 关注点分离:集合类专注于数据管理,迭代逻辑交给迭代器
- 代码更简洁:只需实现
getIterator()一个方法 - 灵活性:可以返回不同类型的迭代器(正序、逆序、过滤等)
- 可组合:可以轻松创建多种遍历方式
foreach 与 IteratorAggregate
php
<?php
declare(strict_types=1);
// foreach 处理 IteratorAggregate 的步骤:
// 1. 调用 getIterator() 获取迭代器
// 2. 对返回的迭代器调用 rewind()
// 3. 循环: valid() -> key()/current() -> next()IteratorAggregate 与 Countable
php
<?php
declare(strict_types=1);
class MyCollection implements \IteratorAggregate, \Countable
{
/** @var array<int, mixed> */
private array $items = [];
public function add(mixed $item): void
{
$this->items[] = $item;
}
public function count(): int
{
return count($this->items);
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->items);
}
}实战示例
实战:带缓存的集合
php
<?php
declare(strict_types=1);
class LazyCollection implements \IteratorAggregate
{
private \Closure $factory;
private ?array $cachedItems = null;
public function __construct(\Closure $factory)
{
$this->factory = $factory;
}
private function load(): array
{
if ($this->cachedItems === null) {
$this->cachedItems = ($this->factory)();
}
return $this->cachedItems;
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->load());
}
public function filter(callable $callback): self
{
return new self(function () use ($callback): array {
return array_filter($this->load(), $callback);
});
}
public function map(callable $callback): self
{
return new self(function () use ($callback): array {
return array_map($callback, $this->load());
});
}
public function toArray(): array
{
return $this->load();
}
}
$users = new LazyCollection(fn() => ['Alice', 'Bob', 'Charlie']);
$admins = $users->filter(fn(string $name): bool => $name !== 'Bob');
$upper = $admins->map(fn(string $name): string => strtoupper($name));
print_r($upper->toArray());
// Array ( [0] => ALICE [2] => CHARLIE )注意事项
getIterator 的返回类型
getIterator() 必须返回一个 Traversable 对象。返回数组或其他类型会导致运行时错误。
多次 foreach 行为
由于 getIterator() 每次都创建新的迭代器实例,IteratorAggregate 支持多次 foreach 遍历。
最佳实践
- 使用 ArrayIterator:对于简单的集合,使用
ArrayIterator即可。 - 使用 Generator:对于需要过滤或变换的场景,使用 Generator 作为迭代器。
- 实现 Countable:如果集合大小是已知的,同时实现
Countable。 - 保持不可变性:集合类最好是不可变的(使用
readonly),避免在遍历中修改数据。
php
<?php
declare(strict_types=1);
readonly class ImmutableCollection implements \IteratorAggregate, \Countable
{
public function __construct(
private array $items
) {}
public function count(): int
{
return count($this->items);
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->items);
}
public function withAdded(mixed $item): self
{
$newItems = $this->items;
$newItems[] = $item;
return new self($newItems);
}
}IteratorAggregate 的高级用法
与 Generator 结合
IteratorAggregate 的 getIterator() 方法可以返回一个 Generator,这样可以利用 yield 的惰性求值优势:
php
<?php
declare(strict_types=1);
class LazyCollection implements \IteratorAggregate
{
/** @var callable */
private $source;
public function __construct(callable $source)
{
$this->source = $source;
}
public function getIterator(): \Traversable
{
$items = ($this->source)();
foreach ($items as $key => $value) {
yield $key => $value;
}
}
}
// 从文件逐行读取,不一次性加载到内存
$collection = new LazyCollection(function () {
$handle = fopen('/path/to/large-file.csv', 'r');
if ($handle === false) return;
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
fclose($handle);
});
foreach ($collection as $line) {
echo $line . "\n";
}实现不可变集合
php
<?php
declare(strict_types=1);
class ImmutableArrayCollection implements \IteratorAggregate, \Countable
{
/** @var array<string, mixed> */
private array $items;
public function __construct(array $items = [])
{
$this->items = $items;
}
public function with(string $key, mixed $value): self
{
$new = clone $this;
$new->items[$key] = $value;
return $new;
}
public function without(string $key): self
{
$new = clone $this;
unset($new->items[$key]);
return $new;
}
public function get(string $key, mixed $default = null): mixed
{
return $this->items[$key] ?? $default;
}
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->items);
}
public function count(): int
{
return count($this->items);
}
}
$config = new ImmutableArrayCollection(['debug' => false, 'cache' => true]);
$updated = $config->with('debug', true);
// 原始 $config 不变
echo $config->get('debug') ? 'true' : 'false'; // false
echo $updated->get('debug') ? 'true' : 'false'; // true多层嵌套集合
php
<?php
declare(strict_types=1);
class GroupedCollection implements \IteratorAggregate
{
/** @var array<string, array> */
private array $groups = [];
public function add(string $group, mixed $item): void
{
$this->groups[$group][] = $item;
}
public function getIterator(): \Traversable
{
foreach ($this->groups as $groupName => $items) {
yield $groupName => new \ArrayIterator($items);
}
}
}
$collection = new GroupedCollection();
$collection->add('fruits', 'apple');
$collection->add('fruits', 'banana');
$collection->add('vegetables', 'carrot');
foreach ($collection as $group => $items) {
echo "{$group}: " . implode(', ', iterator_to_array($items)) . "\n";
}IteratorAggregate vs Iterator 选择指南
| 考量因素 | 使用 Iterator | 使用 IteratorAggregate |
|---|---|---|
| 实现复杂度 | 需要实现 5 个方法 | 只需 1 个方法 |
| 迭代逻辑复杂度 | 简单的数组遍历 | 复杂的数据转换 |
| 外部迭代器支持 | 不支持 | 支持(可更换迭代器) |
| Generator 集成 | 不直接支持 | 可以 yield 返回 |
| 状态管理 | 内部维护迭代状态 | 委托给外部迭代器 |
| 组合模式 | 不易组合 | 易于组合和嵌套 |
常见误区与 FAQ
getIterator 可以返回 IteratorAggregate 吗?
技术上可以,但 foreach 会自动解包。实际上应返回 Iterator 或 Generator。
IteratorAggregate 对象可以被多次遍历吗?
取决于 getIterator() 的实现。如果每次都返回新的迭代器实例,就可以多次遍历。如果返回共享状态的迭代器,可能需要先 rewind。
如何让自定义集合支持链式操作?
php
<?php
declare(strict_types=1);
class Collection implements \IteratorAggregate
{
private array $items;
public function __construct(array $items = [])
{
$this->items = $items;
}
public function filter(callable $callback): self
{
return new self(array_filter($this->items, $callback));
}
public function map(callable $callback): self
{
return new self(array_map($callback, $this->items));
}
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->items);
}
}
$result = (new Collection([1, 2, 3, 4, 5]))
->filter(fn(int $n): bool => $n % 2 === 0)
->map(fn(int $n): string => "item-{$n}");
foreach ($result as $item) {
echo $item . "\n";
}