ZetCode

PHP 迭代器教程

最后修改于 2025 年 5 月 21 日

在本文中,我们探讨了 PHP 迭代器,这是一个强大的功能,允许对任何数据结构进行自定义迭代模式。迭代器提供了一种标准化的方式来遍历不同的数据源,同时保持迭代逻辑和数据本身之间的清晰分离。

PHP 的 Iterator 接口使对象能够使用 foreach 循环进行迭代,从而使开发人员能够精细地控制迭代过程。与具有固定迭代行为的数组不同,自定义迭代器可以实现复杂的遍历逻辑、过滤或按需数据加载。

PHP 迭代器的优点是

通过使用迭代器,开发人员可以创建灵活、可重用的迭代模式,这些模式可以与 PHP 的 foreach 结构无缝协作,同时保持关注点的清晰分离。

Iterator 接口

PHP 迭代系统的核心是 Iterator 接口,它需要实现五个方法:currentkeynextrewindvalid。这些方法提供了对迭代过程的完全控制。

basic_iterator.php
<?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) 中提供了几个有用的内置迭代器,用于解决常见的迭代问题。这些包括 ArrayIteratorFilterIteratorLimitIterator 等。

builtin_iterators.php
<?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

iterator_aggregate.php
<?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 迭代器时,请遵循以下最佳实践,以创建可靠、可维护和高效的迭代代码。

best_practices.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 迭代器为自定义迭代模式提供了一个强大的抽象。要记住的关键点是

在处理大型数据集、复杂数据结构或需要特殊遍历行为时,迭代器特别有用。它们构成了 PHP 对象迭代功能的基础,并支持清晰、高效的数据处理模式。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出 所有 PHP 教程