PHP生成器(generator)和协程的实现方法详解

PHP 5.5 以来,新的诸多特性又一次令 PHP 焕发新的光彩,虽然在本文写的时候已是 PHP 7 alpha 2 发布后的一段时间,但此时国内依旧是 php 5.3 的天下。不过我认为新的特性迟早会因为旧的版本的逐渐消失而变得越发重要,尤其是 PHP 7 的正式版出来后,因此本文的目的就是为了在这之前,帮助一些 PHPer 了解一些他们从没有了解的东西。所以打算将以本篇作为博客中 PHP 知识补全 系列文章的开篇。

其实在写本文之前,我对生成器以及基于此特性延伸出来的 php 的协程实现并没有比较直观的了解,主要是我个人水平并不是很高,属于典型的刚入了门的 PHPer。所以在看了前段时间鸟哥(laruence)博客中对协程的讲解(参考链接:《PHP中使用协同程序实现合作多任务》)后,在我个人对本篇的理解上,针对那些比较难以理解的概念(包括我个人在理解这一概念的时候的难点),以一个更为通俗的方式去讲明白。当然由于本人也是刚刚去学习这一概念,所以有些不得当的地方在所难免,希望大神看见了请不吝赐教。

一切从 Iterator 和 Generator 开始

为便于新入门开发者理解,本文一半篇幅是讲述迭代器接口(Iterator)和 Generator 类的,对此已经理解的话,可以直接跳过。

迭代和迭代器

在理解本文大多数概念前,有必要知道迭代和迭代器。事实上,迭代大家都知道是什么,可是我不知道(真的,在此之前对这个概念没有系统了解)。迭代是指反复执行一个过程,每执行一次叫做一次迭代。实际上我们经常做这种事情,比如:

<?php $mapping = [ 'red' => '#FF0000', 'green' => '#00FF00', 'blue' => '#0000FF' ]; foreach ($mapping as $key => $value) { printf("key: %d - value: %s\n", $key, $value); }

我们可以看到通过 foreach 对数组遍历并迭代输出其内容。在这一环节中,我们需要关注的重点是数组。虽然我们迭代的过程是 foreach 语句中的代码块,但实际上数组 $mapping 在每一次迭代中发生了变化,意味着数组内部也存在着一次迭代。如果我们把数组看做一个对象,foreach 实际上在每一次迭代过程都会调用该对象的一个方法,让数组在自己内部进行一次变动(迭代),随后通过另一个方法取出当前数组对象的键和值。这样一个可通过外部遍历其内部数据的对象就是一个迭代器对象,其遵循的统一的访问接口就是迭代器接口(Iterator)。

PHP 提供了一个统一的迭代器接口。关于迭代器 PHP 官方文档有更为详细的描述,建议去了解。

interface Iterator extends Traversable { /** * 获取当前内部标量指向的元素的数据 */ public mixed current ( void ) /** * 获取当前标量 */ public scalar key ( void ) /** * 移动到下一个标量 */ public void next ( void ) /** * 重置标量 */ public void rewind ( void ) /** * 检查当前标量是否有效 */ public boolean valid ( void ) }

我们来给出一个实例,去实现一个简单的迭代器:

class Xrange implements Iterator { protected $start; protected $limit; protected $step; protected $i; public function __construct($start, $limit, $step = 0) { $this->start = $start; $this->limit = $limit; $this->step = $step; } public function rewind() { $this->i = $this->start; } public function next() { $this->i += $this->step; } public function current() { return $this->i; } public function key() { return $this->i + 1; } public function valid() { return $this->i <= $this->limit; } }

通过 foreach 遍历来看看这个迭代器的效果:

foreach (new Xrange(0, 10, 2) as $key => $value) { printf("%d %d\n", $key, $value); }

输出:

1 0
3 2
5 4
7 6
9 8
11 10

至此我们看到了一个迭代器的实现。一些人在了解这一特性会很激动的将其应用在实际项目中,但有些则疑惑这有什么卵用呢?迭代器只是将一个普通对象变成了一个可被遍历的对象,这在有些时候,如一个对象 StudentsContact,这个对象是用于处理学生联系方式的,通过 addStudent 方法注册学生,通过 getAllStudent 获取全部注册的学生联系方式数组。我们以往遍历是通过 StudentsContact::getAllStudent() 获取一个数组然后遍历该数组,但是现在有了迭代器,只要这个类继承这个接口,就可以直接遍历该对象获取学生数组,并且可以在获取之前在类的内部就对输出的数据做好处理工作。

当然用处远不止这么点,但在这里就不过多纠结。有一个在此基础上更为强大的东西,生成器。

生成器,Generator

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/f2394f10487e0639d6fbb7c5cc21bb94.html