一、什么是闭包
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');