PHP闭包和匿名函数
闭包和匿名函数在PHP5.3.0中被引入。
闭包
闭包是指创建时封装周围环境的函数。即使闭包所在的环境不存在了,闭包中封装的状态依然存在。这个概念很难理解,不过没关系,继续看下去就会明白了。
匿名函数
匿名函数就是没有名字的函数。匿名函数可以赋值给变量,还能像其他任何PHP对象那样传递。不过匿名函数仍然是函数,因此可以调用,可以传递参数。匿名函数特别适合作为函数或者方法的回调。
注意:理论上来说,闭包和匿名函数是不同的概念。不过,PHP将其视作相同的概念。
PHP闭包和匿名函数使用的句法和普通函数相同,但是要注意,闭包和匿名函数其实是伪装成函数的对象,如果你审查PHP闭包或匿名函数,就会发现它们是 Closure类的实例。
创建闭包
举个🌰
<?php $closure = function ($name){ return sprintf('Hello %s', $name); }; echo get_class($closure); //输出 "Closure" echo $closure('Martini'); // 输出 "Hello Martini"
如上,我们创建了一个简单的闭包对象,然后将它赋值给了$closure变量。
我们之所以能调用$closure变量,是因为这个变量的值是一个闭包,并且闭包对象实现了
__invoke()
魔术方法。只要变量名后有(),PHP就会查找并调用__invoke()
方法。
常用地方
我通常PHP闭包当做函数和方法的回调使用。PHP很多函数都会用到回调函数,如arrar_map()
和preg_replace_callback()
。记住,闭包和其他值一样,可以作为参数传入其他PHP函数。
举个🌰
<?php $numberPlusOne = array_map(function($number){ return $number + 1; }, [1,2,3]); print_r($numberPlusOne); // 输出 Array ( [0] => 2 [1] => 3 [2] => 4 )
附加状态
前面说的都是基础,重点来了。
前面我演示了如何把匿名函数当做回调使用,下面探讨如何为PHP闭包附加状态并封装状态。JavaScript开发者可能会对PHP闭包感觉奇怪,因为PHP闭包不会像真正的JavaScript闭包那样自动封装应用的状态。在PHP中必须手动调用闭包对象的bindTo()
方法或者使用use关键字,把状态附加到PHP闭包上。
use 关键字
使用use关键字附加闭包状态很常见,因此先看这种方式。
举个🌰,使用 use 关键字附加闭包状态
<?php function enclosePerson($name){ return function($doCommand) use($name) { return sprintf('%s, %s', $name, $doCommand); }; } // 把字符串"Jack"封装到闭包中 $jack = enclosePerson('Jack'); // 传入参数,调用闭包 echo $jack('get me sweet tea!'); // 输出 "Jack, get me sweet tea!"
具名函数
enclosePerson()
有个名为$name
的参数,这个函数返回一个闭包对象,而且这个对象中封装了$name
参数。即使返回的闭包对象跳出了enclosePerson()
函数的作用域,它也会记住$name
的值,因为$name
变量仍在闭包中。
注意:使用 use 关键字可以把多个参数传入闭包,此时要像PHP函数或方法的参数一样,使用逗号分隔多个参数。
bindTo()
别忘了,PHP闭包是对象,与其他PHP对象类似,每个闭包实例都可以使用$this
关键字获取闭包的内部状态。闭包的默认方法没什么用,不过有一个__invoke()
魔术方法和bindTo()
方法,仅此而已。
但是,bindTo()
方法为闭包增加了一些有趣的潜力。我们可以使用这个方法把Closure对象的内部状态绑定到其他对象上。bindTo()
方法的第二个参数很重要,其作用是指定绑定闭包的那个对象所属的PHP类。因此,闭包可以访问绑定闭包的对象中受保护和私有的成员变量。
你会发现,PHP框架经常使用bindto()
方法把路由URL隐射到匿名回调函数上。框架会把匿名函数绑定到应用对象上,这么做可以在这个匿名函数中使用$this
关键字引用重要的应用对象。
举个🌰,使用bindTo()
方法附加闭包的状态
<?php class App { protected $routes = array(); protected $responseStatus = '200 OK'; protected $responseContentTyep = 'text/html'; protected $responseBody = 'Hello World'; public function addRoute($routePath, $routeCallBack) { $this->routes[$routePath] = $routeCallBack->bindTo($this, __CLASS__); } public function dispatch($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: ' . mb_strlen($this->responseBody)); echo $this->responseBody; } }
<?php $app = new App(); $app->addRoute('/users/martini', function() { $this->responseContentType = 'application/json;charset-utf8'; $this->responseBody = '{"name": "martini"}'; }); $app->dispatch('/users/martini'); // 输出 {"name": "martini"}