Generator和Closure

今天看了两块内容,一个是 Generator 还有一个是 Closures。这里面的话第一个比较简单,应该算是迭代器的变种,第二个闭包就比较麻烦了,以前就不是很懂,所以今天就仔细查了一些资料,争取对闭包能有更深入的了解。

Generator

首先讲下今天看的 Generator,以前学基础的时候确实没听过这个(看来确实学的太水了😂),了解了一下,听作者将,这个的优点就是耗资源少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function myGenerator()
{
yield 'asdfas';
yield '1111';
yield 'cccc';
}
foreach (myGenerator() as $yieldedValue) {
echo $yieldedValue, PHP_EOL;
}
//Result:
//asdfas
//1111
//cccc
?>

上面就是这个 Generator 的用法,它的特性是,一开始不会将 yield 后面的值储存进内存中,只有当调用这个函数的时候,才会像 return 那样一个个取出来。这里就会有一个好处,所以作者提出这样一个问题。

Imagine you need to iterate a 4 GB comma-separated value (CSV) file and your virtual private server (VPS) has only 1 GB ofmemory available to PHP. There’s no way you can pull the entire file into memory.

这种情况下用数组什么的肯定爆掉嘛。这时候 Generator 的威力就体现出来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
function getRows($file)
{
$handle = fopen($file, 'rb');
if ($handle === false)
throw new Exception();
while (feof($handle) === false)
yield fgetcsv($handle);
fclose($handle);
}
foreach (getRows('data.csv') as $row)
print_r($row);
?>

因为 Generator 是一个一个yield这样读取的,所以在上面的那个方法,我们可以一次一次获取数据,而不是一下子将数据都获取过来。总而言之 Generator 就像 return 但它是一个可以多次使用的 return,是 PHP 里的一个语法糖。

Closure

这个就是一个很头疼的东西了。作者说在 PHP 里闭包就是匿名函数,我感觉以前又没好好学基础,如果他这样讲我就稍微理解了点,用法大概也清楚了点。

1
2
3
4
5
6
7
<?php
$closure = function ($name)
{
return printf('Hello %s', $name);
};
echo $closure("robinson");

这是一个简单的匿名函数的例子。

那匿名函数的优点在哪呢?

1
2
3
4
5
6
7
<?php
function incrementNumber ($number)
{
return $number + 1;
}
$numbersPlusOne = array_map('incrementNumber', [1,2,3]);
echo($numbersPlusOne);

array_map函数需要传入一个 callback 所以我们需要先定义一个函数然后传进去。

1
2
3
4
5
6
<?php
$numbersPlusOne = array_map(function ($number) {
return $number + 1;
}, [1,2,3]);
print_r($numbersPlusOne);

但是如果我们用了匿名函数的话,就不用像最上面那个例子那样再定义一个函数了。它在只需要使用一次函数的场景下十分有用和简洁。

我们在其他函数中要传入一个闭包要怎么做呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$closure = function ($name)
{
return printf('Hello %s', $name);
};
echo $closure("robinson");
function myCallback()
{
echo "myCallback";
}
function sayHello($callback)
{
echo "hello world";
call_user_func($callback);
}
sayHello('myCallback');
?>

如果我们要将外面的值传进闭包,并且要同时改变他的值,这时候就要使用 &use

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function func($val)
{
$foo = function () use (&$val) {
$val++;
};
$foo();
return $val;
}
echo(func(0));
// Result:
// 1

有时候我们要动态的给我们的类添加一个方法怎么办

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Foo
{
private $name;
public function __construct($name)
{
$this->name = $name;
}
}
$obj = new Foo('robin');
$say = function ()
{
return "hello world" . $this->name;
};
$say = $say->bindTo($obj, $obj);
// $obj->say->__invoke();
echo ($obj->say->__invoke());
?>

最好的办法是使用bindTo(),这个方法能将对象注入到我们的闭包中,从而实现在闭包中调用对象本身具有的值,给人一种动态增加方法的的错觉,但确实是很好的模拟了这种特性。

这次终于了解了bindTo()是怎么用的,以前看框架源码的时候一直不知道是干嘛的。