生成器
PHP生成器(generator)是PHP5.5.0引入的功能,我估计很多小伙伴都没用过,甚至不知道生成器这个特性的存在,因为生成器的作用不是很明显。生成器是简单点迭代器,仅此而已。
与标准的PHP迭代器不同,PHP生成器不要求类实现Itereaor(不知道这个是什么?看补充迭代器部分)接口,从而减轻了类的负担。假如标准的PHP迭代器经常在内存中执行迭代操作,这要预先计算出数据集,性能低下;如果使用特定的方式计算大量数据,对性能的影响更甚。此时我们可以使用生成器,及时计算并产出后续值,不占用宝贵的内存资源。
简单补充迭代器
很多小伙伴不知道迭代器是什么,也就是Itereaor接口是什么。
PHP官方简介:可在内部迭代自己的外部迭代器或类的接口。(太抽象,看不懂?)
个人理解:高级foreach循环,能够让对象像数组一样循环。
其实foreach也能循环对象,区别在于:如果你的类并实现了Iterator接口,那么你的这个类对象就是ZEND_ITER_OBJECT,否则就是ZEND_ITER_PLAIN_OBJECT。对于ZEND_ITER_PLAIN_OBJECT的类,foreach会通过HASH_OF获取该对象的默认属性数组,然后对该数组进行foreach,而对于ZEND_ITER_OBJECT的类对象,则会通过调用对象实现的Iterator接口相关函数来进行foreach。
看不懂没关系,基本你也不会去用,哈哈哈,这里只是想说明一个问题,不管你使用foreach还是自己创建的迭代器,你要循环的数据是预先生成的,这个数据会加载到内存,提供给迭代器循环输出,那么这个数据如果超过最大内容限制,会提示内存不足,当然你可以使用 ini_set('memory_limit', '内存大小');来临时改变内存,但是如果还是不够怎么办,例如:我现在有个日志文件,大小是5G,服务器的内存2G,你全开都不够啊,哈哈哈哈哈,怎么办呢?生成器就能给我解决这个问题,哈哈哈,大部分我使用生成器也是读文件在用。
注意:PHP生成器不能满足所有迭代操作的需求,因为如果不查询,生成器永远不知道下一个要迭代的值是什么,这里提到一个开发中常遇到的问题来举例:数据库操作,有时候对大批量数据进行处理,如果把数据全部查询出来,很多小伙伴会遇到内存溢出,那么多数据保存在内存中,肯定不足啊,这里的解决方案就是分块拿,每次拿1000,用完再拿1000,这个解决方案在各个框架都有,例如laravel中等chunk(),tp5中也是chunk(),分块拿,小伙伴可以定位到这个chunk(),里面是do{}while{}循环在处理,那么问题来了,能否用今天我们探究的生成器来解决呢?答案是no,至于为什么,因为生成器永远不知道下一个要迭代的值是什么,这里对数据库的操作,我们只能采取数据拿出才能解决,难道你打算每次读取一条,那么这样数据库资源更大,不建议。
创建生成器
生成器的创建很简单,只是一个PHP函数,使用yield关键字。与普通的PHP函数不同的是,生成器从不返回值,只产出值,和return是有区别的。
<?php
function myGenerator() {
yield 'test1';
}
定义生成器是很容易的,就和定义函数一样,我们函数使用return 返回,换成yield 直接产值就好,简单吧,最主要我们来看看生成器常见的使用吧。
使用生成器
下面的代码是我常使用生成器的案例,欢迎在其他地方也能使用生成器的小伙伴反馈,共同成长。
<?php
// 定义一个生成器
function readeFile($file)
{
$handle = fopen($file, 'r');// 只读打开文件得到文件资源
if ($handle == false) { // 打开失败抛出异常
throw new Exception();
}
while (feof($handle) === false) {// 判断是否到文件最后
yield fgets($handle); // 读取一行
}
fclose($handle);// 关闭文件资源文件
}
$file = 'text.txt';
// 逐行读取
foreach (readeFile($file) as $row) {
print_r($row);
}
当text.txt文件大于你的内存时 也不会出问题哟!
闭包
闭包和匿名函数是PHP5.3.0中引入,这两个特性是仅次于命名空间使用最多的吧,或多或少,你在开发中肯定使用了的,这两个特性很有必要去掌握(好用+使用频率超高,上面提到的生成器不用也没事儿,但是这两个特性不能错过)。
闭包是指在创建时封装周围状态的函数。
匿名函数是没有名称的函数。
理论上闭包和匿名函数两个不同的函数,但是在PHP中其实一样,虽然闭包和匿名函数的使用和普通函数相同,但是闭包和匿名函数是对象,你没有看错,他们是Closure类的实例,伪装成函数的对象,哈哈哈哈哈,吓到了没。
创建闭包
先来看简单的闭包使用:
<?php
$closure = function ($name) {
return "hello " . $name;
};
echo $closure('phper');
输出结果是:hello phper
我们使用闭包最常见的是结合PHP自带的一些函数,会用到回调函数,比如array_map(),preg_replace_callback(),直接看写法,比较简单:
<?php
// 写法一
// 匿名函数直接放在参数位置
$res1 = array_map(function ($value) {
return $value + 1;
}, [1, 2, 3]);
var_dump($res1);
// 写法二
// 定义函数
function doMap($value)
{
return $value + 2;
}
// 函数名当参数
$res2 = array_map('doMap', [1, 2, 3]);
var_dump($res2);
输出结果分别是:
array(3) {
[0]=>
int(2)
[1]=>
int(3)
[2]=>
int(4)
}
array(3) {
[0]=>
int(3)
[1]=>
int(4)
[2]=>
int(5)
}
还有一些PHP自带函数使用回调函数,欢迎小伙伴们留言整理,哪些函数会用到匿名函数,我先举例几个吧:
array_map(),preg_replace_callback(),array_diff_uassoc(),array_intersect_uassoc() 我只选了几个数组相关的函数,还有很多,欢迎小伙伴们留言,请顺带解释下函数的用法,方便大家使用嘛,哈哈哈。
那么问题又来了,上面讲的都是PHP自带的函数使用匿名函数来处理,如何自己来写或者说开发中我们怎么自己来写,那就让我们来提升下吧,用上面提及到的 laravel中等chunk() 这个方法来举例,如何使用,只是简单的模拟,开发中使用根据自身场景!
<?php
class Test
{
// 模拟数据库只有4页数据
private $page = 4;
// 模拟数据库中4页数据
private $dataBase = [
1 => [['name' => 'test1', 'age' => 12], ['name' => 'test2', 'age' => 8]],
2 => [['name' => 'test3', 'age' => 13], ['name' => 'test4', 'age' => 15]],
3 => [['name' => 'test5', 'age' => 4], ['name' => 'test6', 'age' => 23]],
4 => [['name' => 'test7', 'age' => 5], ['name' => 'test8', 'age' => 12]],
];
// 获取结果
public function getResult($page)
{
$dataBase = $this->dataBase;
return $dataBase[$page] ?? [];
}
// 模拟分块处理数据
public function chunk($page, callable $callback)
{
do {
$result = $this->getResult($page);
if (!$result) break;
$callback($result);
$page++;
} while ($page <= $this->page);
return true;
}
}
$test = new Test();
// 从第几页开始取
$test->chunk(1, function ($data) {
foreach ($data as $k => $v) {
$str = 'name: ' . $v['name'] . '-' . 'age: ' . $v['age'] . "\n";
// 直接输出
echo $str;
// 或者写入文件
//file_put_contents('text.txt', $str, FILE_APPEND);
// 想怎么处理 你自己看着办吧 哈哈哈哈哈
}
});
使用相当简单吧,合理使用闭包的写法,会提升代码质量
因为闭包的东西比较多,下篇文章我们将继续深入学习哟!
附上代码demo: https://github.com/lirui310/demo-generator
后续会继续更新,感兴趣的小伙伴可以收藏下哟!
欢迎有问题或者项目开发有问题的小伙伴(妹子优先)添加微信:wxmm686800,共同成长!不好意思可以邮箱联系:lirui310@aliyun.com