PHP 迭代器教程
最后修改于 2025 年 5 月 21 日
在本文中,我们探讨了 PHP 迭代器,这是一个强大的功能,允许对任何数据结构进行自定义迭代模式。迭代器提供了一种标准化的方式来遍历不同的数据源,同时保持迭代逻辑和数据本身之间的清晰分离。
PHP 的 Iterator 接口使对象能够使用 foreach 循环进行迭代,从而使开发人员能够精细地控制迭代过程。与具有固定迭代行为的数组不同,自定义迭代器可以实现复杂的遍历逻辑、过滤或按需数据加载。
PHP 迭代器的优点是
- 自定义遍历逻辑: 为您的数据结构实现任何迭代模式。
- 内存效率: 处理大型数据集,而无需将所有内容加载到内存中。
- 一致的接口: 使用相同的 foreach 语法处理不同的数据源。
- 惰性求值: 仅在迭代期间需要时才加载或计算数据。
- 可组合性: 组合多个迭代器以实现强大的数据处理管道。
通过使用迭代器,开发人员可以创建灵活、可重用的迭代模式,这些模式可以与 PHP 的 foreach 结构无缝协作,同时保持关注点的清晰分离。
Iterator 接口
PHP 迭代系统的核心是 Iterator 接口,它需要实现五个方法:current、key、next、rewind 和 valid。这些方法提供了对迭代过程的完全控制。
<?php
declare(strict_types=1);
class NumberRangeIterator implements Iterator {
private int $start;
private int $end;
private int $current;
private int $key = 0;
public function __construct(int $start, int $end) {
$this->start = $start;
$this->end = $end;
}
public function rewind(): void {
$this->current = $this->start;
$this->key = 0;
}
public function valid(): bool {
return $this->current <= $this->end;
}
public function key(): int {
return $this->key;
}
public function current(): int {
return $this->current;
}
public function next(): void {
$this->current++;
$this->key++;
}
}
// Using the iterator
$iterator = new NumberRangeIterator(5, 10);
foreach ($iterator as $key => $value) {
echo "$key: $value\n";
}
// Manual iteration
$iterator->rewind();
while ($iterator->valid()) {
echo $iterator->current() . "\n";
$iterator->next();
}
此基本迭代器实现演示了核心 Iterator 接口方法。该类在指定的范围内生成数字,并维护其自己的迭代状态。foreach 和手动迭代都会产生相同的结果,这表明 foreach 如何与迭代器方法交互。
λ php basic_iterator.php 0: 5 1: 6 2: 7 3: 8 4: 9 5: 10 5 6 7 8 9 10
内置迭代器
PHP 在标准 PHP 库 (SPL) 中提供了几个有用的内置迭代器,用于解决常见的迭代问题。这些包括 ArrayIterator、FilterIterator、LimitIterator 等。
<?php
declare(strict_types=1);
// ArrayIterator - iterate over arrays with object interface
$array = ['apple', 'banana', 'cherry'];
$arrayIterator = new ArrayIterator($array);
foreach ($arrayIterator as $item) {
echo "$item\n";
}
// FilterIterator - abstract class for custom filtering
class OddNumberFilter extends FilterIterator {
public function accept(): bool {
return $this->current() % 2 !== 0;
}
}
$numbers = new ArrayIterator(range(1, 10));
$oddFilter = new OddNumberFilter($numbers);
echo "Odd numbers: " . implode(', ', iterator_to_array($oddFilter)) . "\n";
// LimitIterator - iterate over a subset of items
$limitIterator = new LimitIterator(
new ArrayIterator(range('a', 'z')),
5, // offset
10 // count
);
echo "Letters 5-14: " . implode(', ', iterator_to_array($limitIterator)) . "\n";
// MultipleIterator - iterate over multiple iterators simultaneously
$names = new ArrayIterator(['Alice', 'Bob', 'Charlie']);
$ages = new ArrayIterator([25, 30, 35]);
$multiIterator = new MultipleIterator(MultipleIterator::MIT_KEYS_ASSOC);
$multiIterator->attachIterator($names, 'name');
$multiIterator->attachIterator($ages, 'age');
foreach ($multiIterator as $person) {
echo "{$person['name']} is {$person['age']} years old\n";
}
// RecursiveArrayIterator - for traversing nested structures
$tree = [
'fruit' => ['apple', 'banana'],
'vegetables' => ['carrot', 'pea']
];
$recursiveIterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($tree)
);
echo "All items: " . implode(', ', iterator_to_array($recursiveIterator, false)) . "\n";
此示例展示了几个强大的 SPL 迭代器。FilterIterator 演示了自定义过滤,而 MultipleIterator 显示了如何组合并行迭代。这些内置工具通常可以帮助您避免编写自定义迭代器实现。
λ php builtin_iterators.php apple banana cherry Odd numbers: 1, 3, 5, 7, 9 Letters 5-14: f, g, h, i, j, k, l, m, n, o Alice is 25 years old Bob is 30 years old Charlie is 35 years old All items: apple, banana, carrot, pea
IteratorAggregate 接口
IteratorAggregate 接口提供了一种更简单的方法,通过将迭代逻辑委托给单独的迭代器来使对象可迭代。它只需要实现一个方法:getIterator。
<?php
declare(strict_types=1);
class ProductCollection implements IteratorAggregate {
private array $products;
public function __construct(array $products) {
$this->products = $products;
}
public function getIterator(): Traversable {
return new ArrayIterator($this->products);
}
public function filter(callable $callback): self {
return new self(array_filter($this->products, $callback));
}
}
// Using the IteratorAggregate
$products = new ProductCollection([
['id' => 1, 'name' => 'Laptop', 'price' => 999.99],
['id' => 2, 'name' => 'Phone', 'price' => 599.99],
['id' => 3, 'name' => 'Tablet', 'price' => 399.99]
]);
echo "All products:\n";
foreach ($products as $product) {
echo "- {$product['name']}: \${$product['price']}\n";
}
// Chaining with filtered results
echo "\nExpensive products:\n";
$expensive = $products->filter(fn($p) => $p['price'] > 500);
foreach ($expensive as $product) {
echo "- {$product['name']}: \${$product['price']}\n";
}
// Custom iterator with IteratorAggregate
class PaginatedResults implements IteratorAggregate {
private int $pageSize;
private int $currentPage = 0;
private array $data;
public function __construct(array $data, int $pageSize = 2) {
$this->data = $data;
$this->pageSize = $pageSize;
}
public function getIterator(): Traversable {
$offset = $this->currentPage * $this->pageSize;
return new ArrayIterator(
array_slice($this->data, $offset, $this->pageSize)
);
}
public function nextPage(): void {
$this->currentPage++;
}
public function hasMore(): bool {
return ($this->currentPage + 1) * $this->pageSize < count($this->data);
}
}
$paginated = new PaginatedResults(range(1, 5));
echo "\nPaginated results:\n";
do {
foreach ($paginated as $item) {
echo "$item ";
}
echo "\n";
$paginated->nextPage();
} while ($paginated->hasMore());
IteratorAggregate 接口通过委托给另一个迭代器来简化类的可迭代性。此示例显示了到 ArrayIterator 的简单委托和更复杂的分页实现。当您希望在您的集合类及其迭代逻辑之间保持分离时,该接口特别有用。
λ php iterator_aggregate.php All products: - Laptop: $999.99 - Phone: $599.99 - Tablet: $399.99 Expensive products: - Laptop: $999.99 - Phone: $599.99 Paginated results: 1 2 3 4
最佳实践
使用 PHP 迭代器时,请遵循以下最佳实践,以创建可靠、可维护和高效的迭代代码。
<?php
declare(strict_types=1);
// 1. Prefer IteratorAggregate for simple cases
class SimpleCollection implements IteratorAggregate {
private array $items;
public function __construct(array $items) {
$this->items = $items;
}
public function getIterator(): Traversable {
return new ArrayIterator($this->items);
}
}
// 2. Use existing SPL iterators when possible
// Instead of writing custom iterators, check if SPL provides one
$filtered = new CallbackFilterIterator(
new ArrayIterator([1, 2, 3, 4, 5]),
fn($n) => $n % 2 === 0
);
// 3. Document iterator behavior
/**
* @implements Iterator<int, User>
*/
class UserIterator implements Iterator {
// ...
}
// 4. Handle resource cleanup
class FileLineIterator implements Iterator {
private $file;
private $current;
private int $key = 0;
public function __construct(string $filename) {
$this->file = fopen($filename, 'r');
if (!$this->file) {
throw new RuntimeException("Cannot open file");
}
}
public function __destruct() {
if (is_resource($this->file)) {
fclose($this->file);
}
}
// ... implement Iterator methods
}
// 5. Consider immutability
// Iterators should generally not modify the underlying data during iteration
// 6. Use iterator_to_array() cautiously
// Converting large iterators to arrays loses memory benefits
// 7. Implement SeekableIterator for random access
class SeekableDataIterator implements SeekableIterator {
private array $data;
private int $position = 0;
public function __construct(array $data) {
$this->data = $data;
}
public function seek($position): void {
if (!isset($this->data[$position])) {
throw new OutOfBoundsException("Invalid position");
}
$this->position = $position;
}
// ... implement other Iterator methods
}
// 8. Type hint for Traversable when appropriate
function processItems(Traversable $items): void {
foreach ($items as $item) {
// Process item
}
}
// 9. Combine iterators for complex processing
$pipeline = new TransformIterator(
new FilterIterator(
new ArrayIterator($data),
fn($x) => $x > 0
),
fn($x) => $x * 2
);
// 10. Consider performance for small datasets
// For small fixed datasets, simple arrays may be more efficient
这些实践有助于确保您的迭代器实现是正确的、高效的且可维护的。要点包括利用现有的 SPL 迭代器、适当的资源管理以及对迭代器行为和类型的清晰文档说明。
PHP 迭代器为自定义迭代模式提供了一个强大的抽象。要记住的关键点是
- 标准化接口: Iterator 接口支持跨不同数据结构的一致迭代。
- 内存效率: 迭代器可以处理大型数据集,而无需将所有内容加载到内存中。
- 灵活性: 为您的特定数据实现任何遍历逻辑。
- SPL 实用程序: PHP 提供了许多用于常见模式的内置迭代器。
- 关注点分离: 将迭代逻辑与数据存储分开。
- 可组合性: 组合迭代器以创建强大的数据处理管道。
- 惰性求值: 仅在迭代期间需要时才处理项目。
在处理大型数据集、复杂数据结构或需要特殊遍历行为时,迭代器特别有用。它们构成了 PHP 对象迭代功能的基础,并支持清晰、高效的数据处理模式。
作者
列出 所有 PHP 教程。