一、概述
闭包和匿名函数在PHP 5.3.0中引入,这两个特性非常有用,每个PHP开发者都应该掌握。
闭包是指在创建时封装周围状态的函数,即使闭包所在的环境不存在了,闭包中封装的状态依然存在。
匿名函数其实就是没有名称的函数,匿名函数可以赋值给变量,还能像其他任何 PHP 函数对象那样传递。不过匿名函数仍然是函数,因此可以调用,还可以传入参数,适合作为函数或方法的回调。
注:理论上讲闭包和匿名函数是不同的概念,不过 PHP 将其视作相同的概念(匿名函数在 PHP 中也叫作闭包函数),所以下面提到闭包时指的也是匿名函数;反之亦然。
二、创建闭包
创建闭包很简单:
$greet = function ($name) {
return sprintf("Hello %s\r\n", $name);
};
echo $greet('lulublog.cn');
结果打印:
Hello lulublog.cn
闭包和普通的PHP函数很像:常用的句法相同,也接受参数,而且能返回值。不过闭包没有函数名。
注:我们之所以能调用 $greet 变量,是因为这个变量的值是一个闭包,而且闭包对象实现了__invoke()魔术方法,只要变量名后有(),PHP就会查找并调用__invoke方法。
闭包函数没有函数名称,直接在 function() 传入变量即可,使用时将定义的变量当作函数来处理
$example = function($message){
return sprintf("hello %s",$message);
};
echo $example("world");
//输出 hello world
我们通常把 PHP 闭包当做函数会方法的回调使用,事实上,很多 PHP 函数都会用到闭包,比如 array_map 和 preg_replace_callback,这是使用 PHP 匿名函数的绝佳时机。
记住,闭包和其他值一样,可以作为参数传入其他PHP函数:
$numberPlusOne = array_map(function ($number) {
return $number += 1;
}, [1, 2, 3]);
print_r($numberPlusOne);
在闭包出现之前,要实现这样的功能,PHP 开发者只能单独创建具名函数,然后使用名称引用这个函数:
function incrementNumber ($number) {
return $number += 1;
}
$numberPlusOne = array_map(‘incrementNumber’, [1, 2, 3]);
print_r($numberPlusOne);
这样做把回调的实现和使用场所隔离开了,而且使用闭包实现代码更加简洁。
三、从父作用域继承变量
在 PHP 中必须手动调用闭包对象的 bindTo 方法或使用 use 关键字把父作用域的变量及状态附加到 PHP 闭包中。
而实际应用中,又以使用 use 关键字实现居多。
使用 use
$message = "hello";
$example = function() use ($message){
return $message;
};
$message = "world";
echo $example();
//输出 hello
使用 use 引用
$message = "hello";
$example = function() use(&$message){
return $message;
};
$message = "world";
echo $example();
//输出 world
使用 use + 正常的传值
$message = "hello";
$example = function ($data) use ($message){
return "{$message},{$data}";
};
echo $example("world");
//输出 hello,world
bindTo 方法
我们在前面已经提到,闭包是一个对象,所以我们可以在闭包中使用$this关键字获取闭包的内部状态,闭包对象的默认状态没什么用,需要注意的是其中的 __invoke 魔术方法和 bindTo 方法。
__invoke 的作用前面已经说过,当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
接下来我们来看看 bindTo 方法,通过该方法,我们可以把闭包的内部状态绑定到其他对象上。
这里 bindTo 方法的第二个参数显得尤为重要,其作用是指定绑定闭包的那个对象所属的 PHP 类,这样,闭包就可以在其他地方访问邦定闭包的对象中受保护和私有的成员变量。