[TOC] #### 1. 迭代器模式 --- PHP 的迭代器模式与 JavaScript 迭代器思想大同小异,可移步参考: [JavaScript 迭代器和生成器](https://www.itqaq.com/index/681.html) 迭代器模式(Iterator Pattern)是一种行为型设计模式,它的核心作用是提供一种方法来顺序访问一个聚合对象(如数组、集合、列表)中的各个元素,而无需暴露对象内部的数据结构。 简单来说: > 不关心数据存储方式,只关心如何遍历数据 它就像是一个 “游标”,让你能够统一、安全地遍历各种复杂的数据结构,而不需要了解这些数据在底层是如何存储的 #### 2. 迭代器的核心机制 --- 在 PHP 中,迭代器不仅是设计模式,更是语言层面的原生支持。其核心是 PHP 标准库(SPL)提供的 `Iterator` 接口 只要类实现了 `Iterator` 接口,就可以直接在 `foreach` 循环中使用,该接口规定了以下 5个必须实现的方法: | 方法名 | 作用说明 | | ------------ | ------------ | | rewind() | 将内部指针重置到第一个元素(循环开始前调用) | | current() | 返回当前指针指向的元素值 | | key() | 返回当前指针指向的键名 | | next() | 将内部指针向下移动一位 | | valid() | 检查当前指针位置是否有效(每次循环都会判断) | ```php interface Iterator { public function current(); public function next(); public function key(); public function valid(); public function rewind(); } ``` #### 3. 自定义迭代器 --- 假设我们有一个书籍集合,想要在不暴露内部数组的情况下进行遍历,可以这样实现: ```php // 定义一个实现了 Iterator 接口的书籍集合类 class BookCollection implements Iterator { private $books = []; private $position = 0; // 记录当前遍历的位置 public function __construct(array $books) { $this->books = $books; } // 重置指针到开头 public function rewind(): void { $this->position = 0; } // 返回当前元素 public function current(): mixed { return $this->books[$this->position]; } // 返回当前键名 public function key(): mixed { return $this->position + 1; // 从1开始计数 } // 指针下移 public function next(): void { ++$this->position; } // 检查当前位置是否还有元素 public function valid(): bool { return isset($this->books[$this->position]); } } // 客户端使用 $books = new BookCollection(['PHP入门', '设计模式详解', 'MySQL进阶']); // 直接像遍历普通数组一样遍历对象 foreach ($books as $key => $book) { echo "第 {$key} 本书: {$book}\n"; } // 输出结果: // 第 1 本书: PHP入门 // 第 2 本书: 设计模式详解 // 第 3 本书: MySQL进阶 ``` #### 4. PHP 内置迭代器 --- ##### DirectoryIterator(目录迭代器) PHP 的 SPL 扩展提供了许多强大的内置的迭代器,可以直接拿来用,例如:遍历目录文件: ```php // DirectoryIterator 是 PHP 内置的用于遍历目录的迭代器 $dir = new DirectoryIterator(__DIR__); foreach ($dir as $fileInfo) { if (!$fileInfo->isDot()) { echo "文件名: " . $fileInfo->getFilename() . "\n"; } } ``` ##### ArrayIterator(数组迭代器) `ArrayIterator` 是最基础的迭代器,它允许你将一个普通的 PHP 数组封装成一个对象来进行遍历 与 `foreach` 相比,它的最大优势在于提供了丰富的方法灵活控制遍历过程,甚至可以在遍历时安全的修改或删除元素 ```php $data = ['name' => 'liang', 'age' => 18, 'gender' => '男']; // 数组迭代器对象 $iterator = new ArrayIterator($data); // 1.基础遍历 foreach ($iterator as $key => $value) { echo "$key => $value\n"; } // 2.遍历时动态修改元素值 foreach ($iterator as $key => $value) { if ($key === 'age') { $iterator->offsetSet($key, 20); // 将 age 修改为 20 } if ($key === 'gender') { $iterator->offsetUnset($key); // 删除元素 } } // 3.使用 ArrayIterator 的方法访问元素 var_dump($iterator['name']); // 输出: string(6) "liang" var_dump($iterator->getArrayCopy()); // ['name' => 'liang', 'age' => 20]; ``` ##### FilterIterator(过滤迭代器) `FilterIterator` 是一个抽象类,主要用于在遍历过程中过滤掉不符合条件的元素 + 创建一个子类并继承它,并且必须实现它的抽象方法 `accept()`,该方法返回 `true` 则保留当前元素 ```php $numbers = new ArrayIterator([1, 2, 3, 4, 5, 6]); // 继承 FilterIterator(抽象类)来创建一个过滤器,筛选出偶数 class EvenFilter extends FilterIterator { // 实现父类的抽象方法,需要实现过滤逻辑 public function accept(): bool { return $this->current() % 2 === 0; } } // 过滤后的结果 $evenIterator = new EvenFilter($numbers); foreach ($evenIterator as $num) { echo $num . " "; // 输出: 2 4 6 } echo "\n"; // 使用 PHP 内置函数,将过滤后的结果转换为普通数组 var_dump(iterator_to_array($evenIterator)); // [2, 4, 6] ``` 如果不想写子类,PHP 还提供了更便捷的 `CallbackFilterIterator`,可以直接传入一个回调函数来实现过滤 ```php $numbers = new ArrayIterator([1, 2, 3, 4, 5, 6]); // 使用 CallbackFilterIterator,直接在构造函数中传入一个回调函数来定义过滤逻辑 $greaterThanThree = new CallbackFilterIterator($numbers, function ($current) { return $current % 2 === 0; // 直接在回调函数中定义过滤逻辑 }); foreach ($greaterThanThree as $num) { echo $num . " "; // 输出: 2 4 6 } echo "\n"; var_dump(iterator_to_array($greaterThanThree)); // [2, 4, 6] ``` 此时,你应该可以想到过滤数组使用 `array_filter()` 函数不是更简单吗 ? + 在绝对大多数日常开发场景中,array_filter() 确实比 FilterIterator 更好用、更直观 + FilterIterator 的核心价值在于解决 array_filter() 搞不定的两个痛点:内存性能和面向对象封装(处理海量数据) ```php $numbers = [1, 2, 3, 4, 5, 6]; $array = array_filter($numbers, function ($number) { return $number % 2 === 0; // 过滤出偶数 }); var_dump($array); ``` ##### LimitIterator(限制迭代器) LimitIterator 的作用非常直观,它允许你遍历一个迭代器的 “限定子集”。 + 通过指定偏移量(offset)和限制数量(limit),可以轻松实现数据的截取或分页功能 + 构造函数接收三个参数:`LimitIterator(迭代器对象, 偏移量, 限制数量)` ```php $fruits = new ArrayIterator(['apple', 'banana', 'cherry', 'date', 'elderberry']); // 1. 截取前 3 个元素(偏移量 0,取 3 个) $limitIterator = new LimitIterator($fruits, 0, 3); foreach ($limitIterator as $fruit) { echo $fruit . " "; // 输出: apple banana cherry } echo "\n"; // 2. 实现简单的分页(模拟获取第 2 页,每页 2 条数据) $page = 2; $pageSize = 2; $offset = ($page - 1) * $pageSize; // 偏移量为 2 $pageIterator = new LimitIterator($fruits, $offset, $pageSize); foreach ($pageIterator as $fruit) { echo $fruit . " "; // 输出: cherry date } ``` ##### 迭代器链式组合使用 SPL 迭代器可以像管道一样组合使用,可以将一个迭代器作为另外一个迭代器的参数,从而以极少的代码量实现复杂逻辑 + 通过这种链式调用,代码不仅逻辑清晰,而且完全符合面向对象的设计原则,极大地提升了代码的可维护性 ```php // 从一个数组中,筛选出偶数,并只取前 2 个 $numbers = new ArrayIterator([1, 2, 3, 4, 5, 6, 7, 8]); // 1. 先过滤出偶数 $evenFilter = new CallbackFilterIterator($numbers, function ($current) { return $current % 2 === 0; }); // 2. 再将过滤后的结果传入 LimitIterator 取前 2 个 $limitedEvens = new LimitIterator($evenFilter, 0, 2); foreach ($limitedEvens as $num) { echo $num . " "; // 输出: 2 4 } ``` #### 5. 生成器(Generator) --- 如果你觉得实现 5 个接口方法太繁琐,PHP 还提供了生成器 > 它使用 `yield` 关键字,能自动创建一个轻量级的迭代器,非常适合处理大数据流 在实际开发中,你可以根据具体需求选择最适合的方式。 + 如果是简单的数据产出,首选生成器; + 如果需要复杂的遍历逻辑控制(如倒序、条件过滤),则建议实现标准的 Iterator 接口 ```php // 使用 yield 关键字定义一个生成器函数 function myGenerator($items) { foreach ($items as $item) { yield $item; // 每次产出一个值,并暂停执行 } } $generator = myGenerator(['苹果', '香蕉', '樱桃']); foreach ($generator as $fruit) { echo "水果: {$fruit}\n"; } ```