Skip to content

对象迭代

概述

PHP 支持使用 foreach 遍历对象中的属性。通过实现 IteratorIteratorAggregate 接口,可以自定义对象的迭代行为。这使得对象可以像数组一样被遍历,适用于集合类、查询结果集、数据模型等场景。

基础概念

对象迭代的三种方式

方式接口适用场景
默认迭代无需实现接口遍历对象的 public 属性
Iterator需实现 5 个方法完全自定义迭代逻辑
IteratorAggregate需实现 1 个方法委托给其他迭代器

默认对象迭代

php
<?php

declare(strict_types=1);

class Product
{
    public string $name = 'Laptop';
    public int $price = 9999;
    protected string $sku = 'SKU-001';
    private string $internal = 'secret';
}

$product = new Product();
foreach ($product as $key => $value) {
    echo "{$key}: {$value}" . PHP_EOL;
}
// name: Laptop
// price: 9999
// 注意:protected 和 private 属性不会被遍历

语法与代码

实现 Iterator 接口

php
<?php

declare(strict_types=1);

class NumberRange implements \Iterator
{
    private int $start;
    private int $end;
    private int $current;

    public function __construct(int $start, int $end)
    {
        $this->start = $start;
        $this->end = $end;
        $this->current = $start;
    }

    public function current(): int
    {
        return $this->current;
    }

    public function key(): int
    {
        return $this->current - $this->start;
    }

    public function next(): void
    {
        $this->current++;
    }

    public function rewind(): void
    {
        $this->current = $this->start;
    }

    public function valid(): bool
    {
        return $this->current <= $this->end;
    }
}

foreach (new NumberRange(1, 5) as $key => $value) {
    echo "{$key} => {$value}" . PHP_EOL;
}
// 0 => 1
// 1 => 2
// 2 => 3
// 3 => 4
// 4 => 5

实现 IteratorAggregate 接口

php
<?php

declare(strict_types=1);

class BookCollection implements \IteratorAggregate
{
    /** @var string[] */
    private array $books = [];

    public function add(string $title): void
    {
        $this->books[] = $title;
    }

    public function getIterator(): \ArrayIterator
    {
        return new \ArrayIterator($this->books);
    }
}

$collection = new BookCollection();
$collection->add('PHP 编程');
$collection->add('设计模式');
$collection->add('算法导论');

foreach ($collection as $index => $title) {
    echo "#{$index}: {$title}" . PHP_EOL;
}

筛选迭代器

php
<?php

declare(strict_types=1);

class UserCollection implements \IteratorAggregate
{
    /** @var array<string, int> */
    private array $users = [];

    public function add(string $name, int $age): void
    {
        $this->users[$name] = $age;
    }

    public function getIterator(): \ArrayIterator
    {
        return new \ArrayIterator($this->users);
    }

    /**
     * 按条件筛选
     */
    public function filter(callable $callback): \Iterator
    {
        $filtered = new \ArrayIterator([]);
        foreach ($this as $name => $age) {
            if ($callback($name, $age)) {
                $filtered[$name] = $age;
            }
        }
        return $filtered;
    }

    /**
     * 获取成年用户
     */
    public function adults(): \Iterator
    {
        return $this->filter(fn(string $name, int $age) => $age >= 18);
    }
}

$users = new UserCollection();
$users->add('Alice', 25);
$users->add('Bob', 16);
$users->add('Charlie', 30);

foreach ($users->adults() as $name => $age) {
    echo "{$name}: {$age}" . PHP_EOL;
}
// Alice: 25
// Charlie: 30

自定义迭代键名

php
<?php

declare(strict_types=1);

class EmployeeList implements \Iterator
{
    /** @var array{id: int, name: string, role: string}[] */
    private array $employees;

    public function __construct(array $employees)
    {
        $this->employees = $employees;
    }

    private int $position = 0;

    public function current(): array
    {
        return $this->employees[$this->position];
    }

    public function key(): int
    {
        return $this->employees[$this->position]['id'];
    }

    public function next(): void
    {
        $this->position++;
    }

    public function rewind(): void
    {
        $this->position = 0;
    }

    public function valid(): bool
    {
        return isset($this->employees[$this->position]);
    }
}

$employees = new EmployeeList([
    ['id' => 100, 'name' => 'Alice', 'role' => 'Engineer'],
    ['id' => 200, 'name' => 'Bob', 'role' => 'Designer'],
]);

foreach ($employees as $id => $emp) {
    echo "{$id}: {$emp['name']} ({$emp['role']})" . PHP_EOL;
}
// 100: Alice (Engineer)
// 200: Bob (Designer)

详细说明

Iterator vs IteratorAggregate

特性IteratorIteratorAggregate
需实现方法数5 个1 个
状态管理需要手动管理(位置指针等)委托给内部迭代器
多次迭代需要重置状态每次创建新的迭代器
适用场景复杂迭代逻辑简单集合包装

迭代器与 foreach 的交互流程

foreach ($collection as $key => $value) {
    // 循环体
}

// 等价于:
$collection->rewind();
while ($collection->valid()) {
    $key = $collection->key();
    $value = $collection->current();
    // 循环体
    $collection->next();
}

多次 foreach 的注意事项

php
<?php

declare(strict_types=1);

// IteratorAggregate 每次创建新迭代器,可安全多次 foreach
$collection = new BookCollection();
$collection->add('A');
$collection->add('B');

foreach ($collection as $book) { echo $book; }  // A, B
foreach ($collection as $book) { echo $book; }  // A, B (重新迭代)

// Iterator 需要手动 rewind()
$range = new NumberRange(1, 3);
foreach ($range as $n) { echo $n; }  // 1, 2, 3
foreach ($range as $n) { echo $n; }  // 空(指针已在末尾)

实战示例

场景一:分页集合

php
<?php

declare(strict_types=1);

class PaginatedCollection implements \IteratorAggregate, \Countable
{
    /** @var mixed[] */
    private array $items;
    private int $page;
    private int $perPage;
    private int $total;

    public function __construct(array $items, int $page, int $perPage, int $total)
    {
        $this->items = $items;
        $this->page = $page;
        $this->perPage = $perPage;
        $this->total = $total;
    }

    public function getIterator(): \ArrayIterator
    {
        return new \ArrayIterator($this->items);
    }

    public function count(): int
    {
        return count($this->items);
    }

    public function totalPages(): int
    {
        return (int) ceil($this->total / $this->perPage);
    }

    public function currentPage(): int
    {
        return $this->page;
    }

    public function hasNextPage(): bool
    {
        return $this->page < $this->totalPages();
    }
}

场景二:惰性迭代器

php
<?php

declare(strict_types=1);

class LazyFileReader implements \Iterator
{
    private $handle;
    private ?string $currentLine = null;
    private int $lineNumber = 0;

    public function __construct(string $filePath)
    {
        $this->handle = fopen($filePath, 'r');
    }

    public function current(): ?string
    {
        return $this->currentLine;
    }

    public function key(): int
    {
        return $this->lineNumber;
    }

    public function next(): void
    {
        $this->currentLine = fgets($this->handle);
        if ($this->currentLine !== false) {
            $this->currentLine = trim($this->currentLine);
        }
        $this->lineNumber++;
    }

    public function rewind(): void
    {
        if ($this->handle) {
            rewind($this->handle);
        }
        $this->currentLine = null;
        $this->lineNumber = 0;
        $this->next();
    }

    public function valid(): bool
    {
        return $this->currentLine !== null && $this->currentLine !== false;
    }

    public function __destruct()
    {
        if ($this->handle) {
            fclose($this->handle);
        }
    }
}

注意事项

注意事项

  • 默认迭代只包含 public 属性,不包含 protected/private
  • Iterator 实现需要手动管理迭代状态
  • Iterator 在第一次 foreach 后不会自动 rewind,需要手动调用
  • 使用 IteratorAggregate 可以避免手动管理迭代状态

小贴士

  • 对于简单集合,使用 IteratorAggregate + ArrayIterator
  • 对于需要惰性加载或复杂迭代逻辑,使用 Iterator
  • 同时实现 Countable 接口以支持 count() 函数

最佳实践

1. 优先使用 IteratorAggregate

php
<?php

declare(strict_types=1);

class MyCollection implements \IteratorAggregate
{
    public function getIterator(): \ArrayIterator
    {
        return new \ArrayIterator($this->items);
    }
}

2. 同时实现 Countable

php
<?php

declare(strict_types=1);

class Items implements \IteratorAggregate, \Countable
{
    public function getIterator(): \ArrayIterator { /* ... */ }
    public function count(): int { return count($this->items); }
}

参考链接