对象迭代
概述
PHP 支持使用 foreach 遍历对象中的属性。通过实现 Iterator 或 IteratorAggregate 接口,可以自定义对象的迭代行为。这使得对象可以像数组一样被遍历,适用于集合类、查询结果集、数据模型等场景。
基础概念
对象迭代的三种方式
| 方式 | 接口 | 适用场景 |
|---|---|---|
| 默认迭代 | 无需实现接口 | 遍历对象的 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
| 特性 | Iterator | IteratorAggregate |
|---|---|---|
| 需实现方法数 | 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); }
}