思考并回答以下问题:
- 闭包是个Closure类吗?
- 闭包在创建时就会为传入的参数、use中的变量创建新的内存,相当于clone一份。怎么理解?
- Closure的bind方法是什么意思?
- 可以在匿名函数中使用$this关键字吗?
在PHP中闭包与匿名函数是一个概念:1
2
3$sayHello = function($name){
echo("Hello $name");
};
在PHP中闭包是像函数的对象,是个Closure类,只是能够像函数一样调用:1
$sayHello("world");
输出:1
Hello world
1 | var_dump($sayHello instanceof Closure); |
输出:1
boolean(true)
闭包在创建时就会为传入的参数、use中的变量创建新的内存,相当于clone一份,因此与函数外部的变量变化无关;1
2
3
4
5
6
7
8
9
10
11$name = 'world';
$sayHello = function() use($name){
echo("Hello $name");
}; // 这里不要忘记结束的;号
$sayHello(); // 必须函数方式调用,即用(), 输出Hello world
$name = 'zj';
$sayHello();// 输出Hello world
如果想使闭包内和外部的变量同步,则use中传入引用即可,就是加个&:1
2
3
4
5
6
7
8
9
10
11$name = 'world';
$sayHello = function() use(&$name){ // 传入了引用
echo("Hello $name");
};//这里不要忘记结束的;号
$sayHello(); // 必须函数方式调用,即用(), 输出Hello world
$name = 'zj';
$sayHello();// 输出Hello zj
Closure类有两个方法:bind()和bindTo()
- Closure::bind — 复制一个闭包,绑定指定的$this对象和类作用域。
- Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。
通过这两个方法可以给类扩展复杂功能,类似策略模式,将实际操作与类定义解耦。比如通过bing为用户类增加行为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20Class User{
public $name = 'Tom';
public $age = 10;
private $action = [];
//...
}
$sayHello = function(){
echo("Hello {$this->name}\n");
};
$swimming = function(){
echo("{$this->name} is swimming\n");
};
$bindSayHello = Closure::bind($sayHello, new User());
$bindSwimming = Closure::bind($swimming, new User());
$bindSayHello();
$bindSwimming();
输出:1
2Hello Tom
Tom is swimming
可以把类写的更优雅点:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36Class user{
public $name = 'Tom';
public $age = 10;
private $action = [];
public function addAction($actionName, $actionFunction){
$this->action[$actionName] = Closure::bind($actionFunction, $this);
}
public function doAction(){
foreach ($this->action as $actionFunction) {
$actionFunction($this->name);
}
}
}
$sayHello = function(){
echo("Hello {$this->name}\n");
};
$swimming = function(){
echo("{$this->name} is swimming\n");
};
$bindSayHello = Closure::bind($sayHello, new user());
$bindSayHello();
$user = new user();
$user->addAction('sayHello', $sayHello);
$user->addAction('swimming', $swimming);
$user->doAction();
// 输出:
// Hello Tom
// Hello Tom
// Tom is swimming
bindTo方法与bind类似,只是通过闭包调用的,将自身绑定到对象或类上。
bind及bindTo方法都有第三个参数,确定绑定的作用域。
发现一个闭包实现中间件的例子,搬来作为补充学习:
1 |
|
输出:1
2
3
4Laravle need a filter middleware
Laravle need a auth middleware
this is a Laravle application
Laravle need a log middleware
打包程序
中间件的执行顺序是由打包函数(pack_middleware)决定,这里返回的闭包实际上相当于:1
2$run = $log($filter($auth($application)));
$run('Laravle');
编写规范
中间件要要满足一定的规范:总是返回一个闭包,闭包中总是传入相同的参数(由主要逻辑决定), 闭包总是返回句柄(handler)的执行结果;
如果中间件的逻辑在返回句柄return $handler($name)前完成,就是前置中间件,否则为后置中间件。
一、什么是闭包
理论上讲,闭包和匿名函数是不同的概念,不过,php将其视作相同的概念。
闭包和匿名函数其实是伪装成函数的对象,如果审查php闭包和匿名函数,会发现他们是Closure类的实例,闭包和字符串或整数一样,也是一等值类型。
二、创建一个闭包1
2
3
4
5$closure = function ($name) {
return sprintf('Hello %s', $name);
};
echo $closure('Yee Jason');
输出 Hello Yee Jason.
之所以能调用$closure变量,是因为这个变量的值是一个闭包,而且闭包对象实现了__invoke()魔术方法,只要变量名后面有(),php就会查并调用__invoke() 方法。
我通常把闭包当做函数和方法的回调使用,很多php函数都会用到回调函数,例如array_map和preg_replace_callback()是使用匿名函数的绝佳时机,记住,闭包和其他值一样,可以作为参数传入其他php函数。1
2
3
4
5$numberPlusOne = array_map(function($number) {
return $number + 1;
}, [1, 2, 3]);
print_r($numberPlusOne);
在PHP闭包之前, php开发者无法选择,只能单独创建具名函数,然后引用那个函数,这么做,代码执行的稍微慢一点, 而且把回调的实现和使用场所隔离开了,传统的php代码:1
2
3
4
5
6
7function incrementNumber($number)
{
return $number + 1;
}
$numberPlusOne = array_map('incrementNumber', [1, 2, 3]);
print_r($numberPlusOne);
以上两个例子输出:1
Array ( [0] => 2 [1] => 3 [2] => 4 )
三、附加状态
前面演示了如何把匿名函数当成回调使用,下面探讨如何为php闭包附加并封装状态,javascript开发者可能对php的闭包感到奇怪,因为php闭包不会像真正的javascript闭包那样自动封装应用的状态,在php中,必须手动调用闭包对象的bindTo()方法或者使用use关键字,把状态附加到php闭包上。
使用use关键字附加闭包状态常见的多,因此我们先看这种方式,使用use关键字把变量附加到闭包上时,附加的变量会记住附件时付给他的值。1
2
3
4
5
6
7
8
9
10function enclosePerson($name)
{
return function ($doCommand) use ($name) {
return sprintf('%s, %s', $name, $doCommand);
};
}
$clay = enclosePerson('Clay');
echo $clay('get me sweet tea!');
以上代码输出:Clay get me sweet tea
使用use关键字,把多个参数传入闭包时,需要还是用,号分隔开。
具名函数enclosePerson()有个名为$name的参数,这个函数返回一个闭包对象,而且这个闭包对象封装了 $name参数, 即便 返回的闭包对象跳出了enclosePerson()函数的作用域,它也会记住$name参数的值,因为$name变量仍在闭包中。
使用bindTo方法附加闭包的状态
别忘了php 闭包是对象,与任何其他的php对象类似,每个闭包实例都可以使用$this关键字获取闭包的内部状态。
闭包对象的默认状态没什么用,不过有一个 __invoke()魔术方法和bindTo()方法,仅此而已。
但是bindTo() 方法为闭包增加了一些有趣的潜力,我们可以使用这个方法把Closure对象的内部状态绑定到其他的对象上,
bindTo() 方法的第二个参数很重要,其作用是指定绑定闭包的那个对象所属的php类,因此闭包可以访问绑定闭包的对象中
受保护和私有的成员变量。
你会发现,php框架经常使用bindTo()方法把路由URL映射到匿名回调函数上,框架会把匿名函数绑定到应用对象上,这么做可以在这个匿名函数中使用$this关键字引用重要的对象。
例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27class APP
{
protected $routes = array();
protected $responseStatus = '200 ok';
protected $responseContentType = 'text/html';
protected $responseBody = 'hello world';
public function addRoute($routePath, $routeCallback)
{
$this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__);
}
public function dispath($currentPath)
{
foreach ($this->routes as $routePath => $callback) {
if ($routePath == $currentPath) {
$callback();
}
}
header('HTTP/1.1'. $this->responseStatus);
header('Content-type' . $this->responseContentType);
header('Content-length' . $this->responseBody);
echo $this->responseBody;
}
}
我们要特别注意addRoute方法,这个方法的参数分别是一个路由路径和路由回调,dispatch() 方法的参数是当前的HTTP请 求的 路径,它会调用匹配的路由回调,我们把路由绑定到当前的App实例上,这么做就能再回调函数中处理App实例的状态 。1
2
3
4
5
6$app = new App();
$app->addRoute('/users/josh', function () {
$this->responseContentType = 'application/json; charset=utf8';
$this->responseBody = '{"name" : "yee Jason"}';
});
$app->dispatch('/users/josh');
匿名函数
提到闭包就不得不想起匿名函数,也叫闭包函数(closures),貌似PHP闭包实现主要就是靠它。声明一个匿名函数是这样:1
2
3$func = function() {
}; //带结束符
可以看到,匿名函数因为没有名字,如果要使用它,需要将其返回给一个变量。匿名函数也像普通函数一样可以声明参数,调用方法也相同:1
2
3
4
5
6$func = function( $param ) {
echo $param;
};
$func( 'some string' );
//输出:
//some string
顺便提一下,PHP在引入闭包之前,也有一个可以创建匿名函数的函数:create function,但是代码逻辑只能写成字符串,这样看起来很晦涩并且不好维护,所以很少有人用。
实现闭包
将匿名函数在普通函数中当做参数传入,也可以被返回。这就实现了一个简单的闭包。
下边有三个例子
1 | //例一 |
连接闭包和外界变量的关键字:USE1
2
3
4
5
6
7
8
9
10
11
12
13function getMoney() {
$rmb = 1;
$dollar = 6;
$func = function() use ( $rmb ) {
echo $rmb;
echo $dollar;
};
$func();
}
getMoney();
//输出:
//1
//报错,找不到dorllar变量
闭包可以保存所在代码块上下文的一些变量和值。PHP在默认情况下,匿名函数不能调用所在代码块的上下文变量,而需要通过使用use关键字。 换一个例子看看:
可以看到,dollar没有在use关键字中声明,在这个匿名函数里也就不能获取到它,所以开发中要注意这个问题。
有人可能会想到,是否可以在匿名函数中改变上下文的变量,但我发现是不可以的:
1 | function getMoney() { |
啊,原来use所引用的也只不过是变量的一个副本而已。但是我想要完全引用变量,而不是复制。 要达到这种效果,其实在变量前加一个 & 符号就可以了:
1 | function getMoney() { |
好,这样匿名函数就可以引用上下文的变量了。如果将匿名函数返回给外界,匿名函数会保存use所引用的变量,而外界则不能得到这些变量,这样形成‘闭包’这个概念可能会更清晰一些。
1 | function getMoneyFunc() { |
总结
PHP闭包的特性并没有太大惊喜,其实用CLASS就可以实现类似甚至强大得多的功能,更不能和js的闭包相提并论,只能期待PHP以后对闭包支持的改进。不过匿名函数还是挺有用的,比如在使用preg_replace_callback等之类的函数可以不用在外部声明回调函数了。
一、闭包总结
把一个闭包转换为某个类的方法(只是这个方法不需要通过对象调用), 这样闭包中的$this、static、self就转换成了对应的对象或类
把闭包当成对象的成员方法或者静态成员方法.1
2Closure::bind($cl1, null, 'A'); //就相当于在类里面加了个静态成员方法
Closure::bind($cl2, new A(), 'A'); //相当于在类里面加了个成员方法
成员方法中使用$this访问对象, 静态成员方法直接使用类名::成员的方法.
但是因为是匿名函数, 没有函数名, 所以返回一个已经绑定$this对象和类作用域的闭包给你使用.
二、闭包基本用法
闭包(Closure)又叫做匿名函数,也就是没有定义名字的函数。比如下面的例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// 定义一个闭包,并把它赋给变量 $f
$f = function () {
return 7;
}
// 使用闭包也很简单
$f(); //这样就调用了闭包,输出 7
// 当然更多的时候是把闭包作为参数(回调函数)传递给函数
function testClosure (Closure $callback) {
return $callback();
}
// $f 作为参数传递给函数 testClosure,如果是普遍函数是没有办法作为testClosure的参数的
testClosure($f);
// 也可以直接将定义的闭包作为参数传递,而不用提前赋给变量
testClosure (function () {
return 7;
});
// 闭包不止可以做函数的参数,也可以作为函数的返回值
function getClosure () {
return function () { return 7; };
}
$c = getClosure(); // 函数返回的闭包就复制给 $c 了
$c(); // 调用闭包,返回 7
三、闭包类(Closure)
定义一个闭包函数,其实是产生了一个闭包类(Closure)的对象,Closure 类摘要如下1
2
3
4Closure {
public static Closure bind (Closure $closure , object $newthis [, mixed $newscope = 'static' ])
public Closure bindTo (object $newthis [, mixed $newscope = 'static' ])
}
方法说明:
Closure::bind: 复制一个闭包,绑定指定的 $this 对象和类作用域。
Closure::bindTo: 复制当前闭包对象,绑定指定的 $this 对象和类作用域。
下面将介绍
Closure::bind和Closure::bindTo
参数和返回值说明:
closure:表示需要绑定的闭包对象。
newthis:表示需要绑定到闭包对象的对象,或者 NULL 创建未绑定的闭包。
newscope:表示想要绑定给闭包的类作用域,可以传入类名或类的示例,默认值是’static’, 表示不改变。
该方法成功时返回一个新的 Closure 对象,失败时返回 FALSE。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36class Animal {
private static $cat = "cat";
private $dog = "dog";
public $pig = "pig";
}
/*
* 获取Animal类静态私有成员属性
*/
$cat = static function() {
return Animal::$cat;
};
/*
* 获取Animal实例私有成员属性
*/
$dog = function() {
return $this->dog;
};
/*
* 获取Animal实例公有成员属性
*/
$pig = function() {
return $this->pig;
};
$bindCat = Closure::bind($cat, null, new Animal());// 给闭包绑定了Animal实例的作用域,但未给闭包绑定$this对象
$bindDog = Closure::bind($dog, new Animal(), 'Animal');// 给闭包绑定了Animal类的作用域,同时将Animal实例对象作为$this对象绑定给闭包
$bindPig = Closure::bind($pig, new Animal());// 将Animal实例对象作为$this对象绑定给闭包,保留闭包原有作用域
echo $bindCat(),'<br>';// 根据绑定规则,允许闭包通过作用域限定操作符获取Animal类静态私有成员属性
echo $bindDog(),'<br>';// 根据绑定规则,允许闭包通过绑定的$this对象(Animal实例对象)获取Animal实例私有成员属性
echo $bindPig(),'<br>';// 根据绑定规则,允许闭包通过绑定的$this对象获取Animal实例公有成员属性
// bindTo与bind类似,是面向对象的调用方式,这里只举一个,其他类比就可以
$bindCat = $cat->bindTo(null, 'Animal');
以上示例输出:1
2
3cat
dog
pig
四、连接闭包和外界变量的关键字:USE
闭包可以保存所在代码块上下文的一些变量和值。PHP在默认情况下,匿名函数不能调用所在代码块的上下文变量,而需要通过使用 use 关键字。1
2
3
4
5
6
7
8
9
10
11
12
13function getMoney() {
$rmb = 1;
$dollar = 6;
$func = function() use ( $rmb ) {
echo $rmb;
echo $dollar;
};
$func();
}
getMoney();
//输出:
//1
//报错,找不到dorllar变量
可以看到,dollar没有在use关键字中声明,在这个匿名函数里也就不能获取到它,所以开发中要注意这个问题。
有人可能会想到,是否可以在匿名函数中改变上下文的变量,但我发现是不可以的:1
2
3
4
5
6
7
8
9
10
11
12
13
14function getMoney() {
$rmb = 1;
$func = function() use ( $rmb ) {
echo $rmb;
//把$rmb的值加1
$rmb++;
};
$func();
echo $rmb;
}
getMoney();
//输出:
//1
//1
原来use所引用的也只不过是变量的一个副本而已。但是我想要完全引用变量,而不是复制。要达到这种效果,其实在变量前加一个 & 符号就可以了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function getMoneyFunc() {
$rmb = 1;
$func = function() use ( &$rmb ) {
echo $rmb;
//把$rmb的值加1
$rmb++;
};
return $func;
}
$getMoney = getMoneyFunc();
$getMoney();
$getMoney();
$getMoney();
//输出:
//1
//2
//3
匿名函数
实现一个简单的匿名函数:
上面就是一个简单的匿名函数,定义一个函数体,将函数体赋值给一个变量(php5.3之后支持该写法)。
实现闭包
1、当做参数传递:
2、将匿名函数返回:
捕获外界变量
闭包: 闭包是词法作用于的体现,一个持有外部环境自由变量的函数就是闭包。闭包体现的是在程序运行过程中,由 “不确定”变为“ 确定” 的过程。
捕获外部变量:在PHP中对捕获这一动作有了更清晰的表现,使用use关键字。如上面例2。
在上面的例2中,匿名函数$func通过use关键字捕获了外部的自由变量$param,在调用时通过传入cFunc()函数的参数123($param此时会变为“确定”状态),进而调用匿名函数时输出“params:456 123”。
use引入的是自由变量的副本。
golang闭包: 在golang中同样通过匿名函数实现了闭包,和PHP不同的是,golang中的闭包是默认会引入上下文的自由变量,且引入的地址,即在闭包函数内部修改变量会在函数外部生效。
PHP Closure类
用于代表匿名函数类。在PHP中定义一个闭包函数其实就是一个Closure类的实例。
类摘要1
2
3
4
5
6
7
8
9Closure {
/* 方法 */
__construct ( void )
public static bind ( Closure $closure , object $newthis [, mixed $newscope = 'static' ] ) : Closure
public bindTo ( object $newthis [, mixed $newscope = 'static' ] ) : Closure
Closure::__construct — 用于禁止实例化的构造函数
Closure::bind — 复制一个闭包,绑定指定的$this对象和类作用域。
Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。
Closure::bind
复制一个闭包,绑定指定的$this对象和类作用域,返回一个新的匿名函数
参数说明:
closure: 需要绑定的匿名函数。
newthis: 需要绑定到匿名函数的对象,或者 NULL 创建未绑定的闭包。( 理解:可以选择是否将匿名函数绑定到一个类对象,若绑定到了一个类对象,则可以在匿名函数内使用 $this ,否则不可使用。 )
newscope: 想要绑定给闭包的类作用域,或者 ‘static’ 表示不改变。如果传入一个对象,则使用这个对象的类型名。类作用域用来决定在闭包中 $this 对象的 私有、保护方法 的可见性。( 理解:如果传入一个类,则可以访问类的static、private、protected属性,否则只能访问public属性。 )
简单理解:可以简单理解为将该匿名函数绑定到一个类或实例。根据参数的不同,可以访问不同的类的属性。
输出:1
2
3
4
5
6
7
8
9
10
11bind cat
string(3) "cat"
bind dog
string(3) "dog"
bind dog2
Fatal error: Using $this when not in object context
Closure::bindTo
Closure::bind()的非静态形式。
小结
PHP通过匿名函数实现闭包。
可以通过将匿名函数作为参数或返回值实现闭包。
可以通过use关键字引入外部变量,且引入的变量副本。
匿名函数均实现了Closure类,且可以通过Closure::bind()方法将匿名函数绑定到某个类。以上是文章全部内容,有需要学习交流的友人请加入Swoole交流群的咱们一起,有问题一起交流,一起进步!前提是你是学技术的。感谢阅读!
面向对象变成语言代码的复用主要采用继承来实现,而函数的复用,就是通过闭包来实现。这就是闭包的设计初衷。
注:PHP里面闭包函数是为了复用函数而设计的语言特性,如果在闭包函数里面访问指定域的变量,使用use关键字来实现。
PHP具有面向函数的编程特性,但是也是面向对象编程语言,PHP 会自动把闭包函数转换成内置类 Closure 的对象实例,依赖Closure 的对象实例又给闭包函数添加了更多的能力。
闭包不能被实例(私有构造函数),也不能被继承(finally 类)。可以通过反射来判断闭包实例是否能被实例,继承。
匿名函数
提到闭包就不得不想起匿名函数,也叫闭包函数(closures),貌似PHP闭包实现主要就是靠它。声明一个匿名函数是这样:
1 | $func = function() { |
闭包的几个作用:
1 减少foreach的循环的代码
比如手册http://php.net/manual/en/functions.anonymous.php 中的例子Cart1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 一个基本的购物车,包括一些已经添加的商品和每种商品的数量。
// 其中有一个方法用来计算购物车中所有商品的总价格。该方法使用了一个closure作为回调函数。
class Cart
{
const PRICE_BUTTER = 1.00;
const PRICE_MILK = 3.00;
const PRICE_EGGS = 6.95;
protected $products = array();
public function add($product, $quantity)
{
$this->products[$product] = $quantity;
}
public function getQuantity($product)
{
return isset($this->products[$product]) ? $this->products[$product] :
FALSE;
}
public function getTotal($tax)
{
$total = 0.00;
$callback =
function ($quantity, $product) use ($tax, &$total)
{
$pricePerItem = constant(__CLASS__ . "::PRICE_" .
strtoupper($product));
$total += ($pricePerItem * $quantity) * ($tax + 1.0);
};
array_walk($this->products, $callback);
return round($total, 2);;
}
}
$my_cart = new Cart;
// 往购物车里添加条目
$my_cart->add('butter', 1);
$my_cart->add('milk', 3);
$my_cart->add('eggs', 6);
// 打出出总价格,其中有 5% 的销售税.
print $my_cart->getTotal(0.05) . "\n";
// The result is 54.29
```
这里如果我们改造getTotal函数必然要使用到foreach
2 减少函数的参数
```php
function html ($code , $id="", $class=""){
if ($id !== "") $id = " id = \"$id\"" ;
$class = ($class !== "")? " class =\"$class\"":">";
$open = "<$code$id$class";
$close = "</$code>";
return function ($inner = "") use ($open, $close){
return "$open$inner$close";};
}
如果是使用平时的方法,我们会把inner放到html函数参数中,这样不管是代码阅读还是使用都不如使用闭包
3 解除递归函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$fib = function($n) use(&$fib) {
if($n == 0 || $n == 1) return 1;
return $fib($n - 1) + $fib($n - 2);
};
echo $fib(2) . "\n"; // 2
$lie = $fib;
$fib = function(){die('error');};//rewrite $fib variable
echo $lie(5); // error because $fib is referenced by closure
```
注意上题中的use使用了&,这里不使用&会出现错误fib(n-1)是找不到function的(前面没有定义fib的类型)
所以想使用闭包解除循环函数的时候就需要使用
```php
<?php
$recursive = function () use (&$recursive){
// The function is now available as $recursive
}
这样的形式
4 关于延迟绑定
如果你需要延迟绑定use里面的变量,你就需要使用引用,否则在定义的时候就会做一份拷贝放到use中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$result = 0;
$one = function()
{ var_dump($result); };
$two = function() use ($result)
{ var_dump($result); };
$three = function() use (&$result)
{ var_dump($result); };
$result++;
$one(); // outputs NULL: $result is not in scope
$two(); // outputs int(0): $result was copied
$three(); // outputs int(1)
使用引用和不使用引用就代表了是调用时赋值,还是申明时候赋值
PHP Closure类是用于代表匿名函数的类,匿名函数(在PHP5.3中被引入)会产生这个类型的对象,Closure类摘要如下:1
2
3
4
5Closure {
__construct ( void )
public static Closure bind (Closure $closure , object $newthis [, mixed $newscope = 'static' ])
public Closure bindTo (object $newthis [, mixed $newscope = 'static' ])
}
方法说明:
- Closure::__construct — 用于禁止实例化的构造函数
- Closure::bind — 复制一个闭包,绑定指定的$this对象和类作用域。
- Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。
除了此处列出的方法,还有一个__invoke 方法。这是为了与其他实现了__invoke()魔术方法 的对象保持一致性,但调用闭包对象的过程与它无关。
下面将介绍Closure::bind和Closure::bindTo。
Closure::bind是Closure::bindTo的静态版本,其说明如下:1
public static Closure bind (Closure $closure , object $newthis [, mixed $newscope = 'static' ])
- closure表示需要绑定的闭包对象。
- newthis表示需要绑定到闭包对象的对象,或者NULL创建未绑定的闭包。
- newscope表示想要绑定给闭包的类作用域,可以传入类名或类的示例,默认值是 ‘static’, 表示不改变。
该方法成功时返回一个新的Closure对象,失败时返回FALSE。
例子说明:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 复制一个闭包,绑定指定的$this对象和类作用域。
*
* @author 疯狂老司机
*/
class Animal {
private static $cat = "cat";
private $dog = "dog";
public $pig = "pig";
}
/*
* 获取Animal类静态私有成员属性
*/
$cat = static function() {
return Animal::$cat;
};
/*
* 获取Animal实例私有成员属性
*/
$dog = function() {
return $this->dog;
};
/*
* 获取Animal实例公有成员属性
*/
$pig = function() {
return $this->pig;
};
$bindCat = Closure::bind($cat, null, new Animal());// 给闭包绑定了Animal实例的作用域,但未给闭包绑定$this对象
$bindDog = Closure::bind($dog, new Animal(), 'Animal');// 给闭包绑定了Animal类的作用域,同时将Animal实例对象作为$this对象绑定给闭包
$bindPig = Closure::bind($pig, new Animal());// 将Animal实例对象作为$this对象绑定给闭包,保留闭包原有作用域
echo $bindCat(),'<br>';// 根据绑定规则,允许闭包通过作用域限定操作符获取Animal类静态私有成员属性
echo $bindDog(),'<br>';// 根据绑定规则,允许闭包通过绑定的$this对象(Animal实例对象)获取Animal实例私有成员属性
echo $bindPig(),'<br>';// 根据绑定规则,允许闭包通过绑定的$this对象获取Animal实例公有成员属性
输出:1
2
3cat
dog
pig
Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域,其说明如下:1
public Closure Closure::bindTo (object $newthis [, mixed $newscope = 'static' ])
newthis表示绑定给闭包对象的一个对象,或者NULL来取消绑定。
newscope表示关联到闭包对象的类作用域,可以传入类名或类的示例,默认值是 ‘static’, 表示不改变。
该方法创建并返回一个闭包对象,它与当前对象绑定了同样变量,但可以绑定不同的对象,也可以绑定新的类作用域。绑定的对象决定了返回的闭包对象中的$this的取值,类作用域决定返回的闭包对象能够调用哪些方法,也就是说,此时$this可以调用的方法,与newscope类作用域相同。
例子1:1
2
3
4
5
6
7
8
function __autoload($class) {
require_once "$class.php";
}
$template = new Template;
$template->render(new Article, 'tpl.php');
Template.php 模板类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 模板类,用于渲染输出
*
* @author 疯狂老司机
*/
class Template{
/**
* 渲染方法
*
* @access public
* @param obj 信息类
* @param string 模板文件名
*/
public function render($context, $tpl){
$closure = function($tpl){
ob_start();
include $tpl;
return ob_end_flush();
};
$closure = $closure->bindTo($context, $context);
$closure($tpl);
}
}
Article.php 信息类1
2
3
4
5
6
7
8
9
10
/**
* 文章信息类
*
* @author 疯狂老司机
*/
class Article{
private $title = "这是文章标题";
private $content = "这是文章内容";
}
tpl.php 模板文件1
2
3
4
5
6
7
8
9
10
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<body>
<h1>echo $this->title; </h1>
<p>echo $this->content; </p>
</body>
</html>
运行时确保以上文件位于同级目录。
输出:1
2这是文章标题
这是文章内容
例子2:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 给类动态添加新方法
*
* @author 疯狂老司机
*/
trait DynamicTrait {
/**
* 自动调用类中存在的方法
*/
public function __call($name, $args) {
if(is_callable($this->$name)){
return call_user_func($this->$name, $args);
}else{
throw new \RuntimeException("Method {$name} does not exist");
}
}
/**
* 添加方法
*/
public function __set($name, $value) {
$this->$name = is_callable($value)?
$value->bindTo($this, $this):
$value;
}
}
/**
* 只带属性不带方法动物类
*
* @author 疯狂老司机
*/
class Animal {
use DynamicTrait;
private $dog = 'dog';
}
$animal = new Animal;
// 往动物类实例中添加一个方法获取实例的私有属性$dog
$animal->getdog = function() {
return $this->dog;
};
echo $animal->getdog();
输出:1
dog
例子3:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* 一个基本的购物车,包括一些已经添加的商品和每种商品的数量
*
* @author 疯狂老司机
*/
class Cart {
// 定义商品价格
const PRICE_BUTTER = 1.00;
const PRICE_MILK = 3.33;
const PRICE_EGGS = 8.88;
protected $products = array();
/**
* 添加商品和数量
*
* @access public
* @param string 商品名称
* @param string 商品数量
*/
public function add($item, $quantity) {
$this->products[$item] = $quantity;
}
/**
* 获取单项商品数量
*
* @access public
* @param string 商品名称
*/
public function getQuantity($item) {
return isset($this->products[$item]) ? $this->products[$item] : FALSE;
}
/**
* 获取总价
*
* @access public
* @param string 税率
*/
public function getTotal($tax) {
$total = 0.00;
$callback = function ($quantity, $item) use ($tax, &$total) {
$pricePerItem = constant(__CLASS__ . "::PRICE_" . strtoupper($item));
$total += ($pricePerItem * $quantity) * ($tax + 1.0);
};
array_walk($this->products, $callback);
return round($total, 2);;
}
}
$my_cart = new Cart;
// 往购物车里添加商品及对应数量
$my_cart->add('butter', 10);
$my_cart->add('milk', 3);
$my_cart->add('eggs', 12);
// 打出出总价格,其中有 5% 的销售税.
echo $my_cart->getTotal(0.05);
输出:1
132.88
补充说明:闭包可以使用USE关键连接外部变量。
总结:合理使用闭包能使代码更加简洁和精炼。
PHP核心特性-匿名函数
提出
在匿名函数出现之前,所有的函数都需要先命名才能使用:1
2
3
4
5
6function increment($value)
{
return $value + 1;
}
array_map('increment', [1, 2, 3]);
有的时候函数可能只需要使用一次,这时候使用匿名函数会使得代码更加简洁直观,同时也避免了函数在其他地方被使用:1
2
3array_map(function($value){
return $value + 1;
}, [1, 2, 3]);
定义和使用
PHP将闭包和匿名函数视为同等概念(本文统称为匿名函数),本质上都是伪装成函数的对象。
匿名函数的本质是对象,因此跟对象一样可将匿名函数赋值给某一变量:1
2
3
4
5$greet = function(string $name){
echo "hello {$name}";
}
$greet("jack") // hello jack
所有的匿名函数都是Closure对象的实例:1
$greet instanceof Closure // true
对象并没有什么父作用域可言,所以需要使用use来手动声明使用的变量:1
2
3
4
5
6
7$num = 1;
$func = function() use($num){
$num = $num + 1;
echo $num;
}
$func(); // 2
echo $num; // 还是 1
如果要让匿名函数中的变量生效,需要使用引用传值:1
2
3
4
5
6
7$num = 1;
$func = function() use(&$num){
$num = $num + 1;
echo $num;
}
$func(); // 2
echo $num; // 2
从PHP5.4开始,在类里面使用匿名函数时,匿名函数的$this将自动绑定到当前类:1
2
3
4
5
6
7
8
9
10
11
12class Foo {
public function bar()
{
return function() {
return $this;
};
}
}
$foo = new Foo();
$obj = $foo->bar(); // Closure()
$obj(); // Foo
如果不想让自动绑定生效,可使用静态匿名函数:1
2
3
4
5
6
7
8
9
10
11class Foo {
public function bar()
{
return static function() {
return $this;
};
}
}
$foo = new Foo();
$obj = $foo->bar(); // Closure()
$obj(); // Using $this when not in object context
匿名函数的本质
匿名函数的本质是Closure对象,包括了以下五个方法1
2
3
4
5
6
7Closure {
private __construct ( void )
public static bind ( Closure $closure , object $newthis [, mixed $newscope = "static" ] ) : Closure
public bindTo ( object $newthis [, mixed $newscope = "static" ] ) : Closure
public call ( object $newthis [, mixed $... ] ) : mixed
public static fromCallable ( callable $callable ) : Closure
}
__construct - 防止匿名函数被实例化1
2$closure = new \Closure();
// PHP Error: Instantiation of 'Closure' is not allowed
Closure::bindTo - 复制当前匿名函数对象,绑定指定的 $this 对象和类作用域。通俗的说,就是手动将匿名函数与指定对象绑定,利用这点,可以扩展对象的功能。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 定义商品类
class Good {
private $price;
public function __construct(float $price)
{
$this->price = $price;
}
}
// 定义一个匿名函数,计算商品的促销价
$addDiscount = function(float $discount = 0.8){
return $this->price * $discount;
}
$good = new Good(100);
// 将匿名函数绑定到 $good 实例,同时指定作用域为 Good
$count = $addDiscount->bindTo($good, Good::class);
$count(); // 80
// 将匿名函数绑定到 $good 实例,但是不指定作用域,将无法访问 $good 的私有属性
$count = $addDiscount->bindTo($good);
$count(); // 报错
Closure::bind - bindTo 方法的静态版本,有两种用法:
用法一:实现与bindTo方法同样的效果1
$count = \Closure::bind($addDiscount, $good, Good::class);
用法二:将匿名函数与类(而不是对象)绑定,记得要将第二个参数设置为 null1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 商品库存为 10
class Good {
static $num = 10;
}
// 每次销售后返回当前库存
$sell = static function() {
return"当前库存为". --static::$num ;
};
// 将静态匿名函数绑定到 Good 类中
$sold = \Closure::bind($sell, null, Good::class);
$sold(); // 当前库存为 9
$sold(); // 当前库存为 8
call - PHP7新增的call方法可以实现绑定并调用匿名函数,除了语法更加简洁外,性能也更高1
2
3
4
5
6// call 版本
$addDiscount->call($good, 0.5); // 绑定并传入参数 0.5,结果为 50
// bindTo 版本
$count = $addDiscount->bindTo($good, Good::class);
$count(0.5); // 50
fromCallable - 将给定的 callable 函数转化成匿名函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Good {
private $price;
public function __construct(float $price)
{
$this->price = $price;
}
}
function addDiscount(float $discount = 0.8){
return $this->price * $discount;
}
$closure = \Closure::fromCallable('addDiscount');
$good = new Good(100);
$count = $closure->bindTo($good);
$count = $closure->bindTo($good, Good::class); // 报错,不能重复绑定作用域
$count(); // 报错,无法访问私有属性
fromCallable 等价于1
2$reflexion = new ReflectionFunction('addDiscount');
$closure = $reflexion->getClosure();
这里有一点需要特别注意的是,无论是fromCallable转化成的闭包,还是使用反射得到的闭包,在使用 bindTo 时,如果第二个参数指定绑定类,会报错1
Cannot rebind scope of closure created by ReflectionFunctionAbstract::getClosure()