Skip to content

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 的优势

  1. 关注点分离:集合类专注于数据管理,迭代逻辑交给迭代器
  2. 代码更简洁:只需实现 getIterator() 一个方法
  3. 灵活性:可以返回不同类型的迭代器(正序、逆序、过滤等)
  4. 可组合:可以轻松创建多种遍历方式

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 遍历。

最佳实践

  1. 使用 ArrayIterator:对于简单的集合,使用 ArrayIterator 即可。
  2. 使用 Generator:对于需要过滤或变换的场景,使用 Generator 作为迭代器。
  3. 实现 Countable:如果集合大小是已知的,同时实现 Countable
  4. 保持不可变性:集合类最好是不可变的(使用 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";
}

参考链接