一、什么是闭包
1、闭包和匿名函数在php5.3.0中两个php新特性,使用的也最多,这两个特性听起来很吓人, 其实很容易理解,这两个特性非常有用,每个php开发者都应该掌握。
2、闭包是指在创建时封装周围状态的函数,即便闭包所在的环境不存在了,闭包中封装的状态依然存在,这个概念很难理解 不过一单掌握了,将会对你的生活带来巨大的变化。
3、匿名函数其实就是没有名称的函数,匿名函数可以赋值给变量,还能像其他任何php对象那样传递,不过匿名函数仍然是 匿名函数,因此可以调用,还可以传入参数,匿名函数特别适合作为函数或方法的回调。 理论上讲,闭包和匿名函数是不同的概念,不过,php将其视作相同的概念,所以,我提到闭包时,指的也是匿名函数, 反之亦然。
4、php闭包和匿名函数使用的句法和普通函数相同,不过别被这一点迷惑了,闭包和匿名函数其实是伪装成函数的对象, 如果审查php闭包和匿名函数,会发现他们是Closure类的实例,闭包和字符串或整数一样,也是一等值类型。
二、创建一个闭包
$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函数。
$numberPlusOne = array_map(function($number) {
return $number + 1;
}, [1, 2, 3]);
print_r($numberPlusOne);
在PHP闭包之前, php开发者无法选择,只能单独创建具名函数,然后引用那个函数,这么做,代码执行的稍微慢一点, 而且把回调的实现和使用场所隔离开了,传统的php代码:
function incrementNumber($number)
{
return $number + 1;
}
$numberPlusOne = array_map('incrementNumber', [1, 2, 3]);
print_r($numberPlusOne);
以上两个例子输出:Array ( [0] => 2 [1] => 3 [2] => 4 )
三、附加状态
前面演示了如何把匿名函数当成回调使用,下面探讨如何为php闭包附加并封装状态,javascript开发者 可能对php的闭包感到奇怪,因为php闭包不会像真正的javascript闭包那样自动封装应用的状态,在php中, 必须手动调用闭包对象的bindTo()方法或者使用use 关键字,把状态附加到php闭包上。 使用 use 关键字附加闭包状态常见的多,因此我们先看这种方式,使用use 关键字把变量附加到闭包上时, 附加的变量会记住附件时付给他的值。
function 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关键字引用重要的对象。 例子:
class 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实例的状态 。
$app = new App();
$app->addRoute('/users/josh', function () {
$this->responseContentType = 'application/json; charset=utf8';
$this->responseBody = '{"name" : "yee Jason"}';
});
$app->dispatch('/users/josh');