首頁 >後端開發 >php教程 >[Modern PHP] 第二章 新特性之五 闭包

[Modern PHP] 第二章 新特性之五 闭包

WBOY
WBOY原創
2016-06-23 13:28:22860瀏覽

闭包


闭包和匿名函数是从PHP 5.3.0开始出现的,这是我最喜欢也是用的最多的PHP功能。听到这些名称心里特别没底(至少我第一次听到时是这么认为的),但是事实上真的很好理解。它们是每个PHP开发者们的工具箱中必备的最有用的工具。


闭包作为一个函数,在创建时会封装外部的状态。即使最初创建闭包时的环境已经不存在了,封装的状态也会一直保存在闭包中。这是一个不太好掌握的概念,一旦你能够弄明白,感觉就像人生翻开了新的篇章。


匿名函数实际上就是没有名字的函数。匿名函数可以被赋值给变量,像所有其它的PHP对象一样在代码中传递。但是它终归还是函数,所以你可以调用它并且传递参数。匿名函数最大的用处是作为函数或者方法的回调。


闭包和匿名函数理论上是不同的概念。然而,PHP认为它们是一码事。所以,当我说闭包的时候也可能指的是匿名函数,反之亦然。


PHP的闭包和匿名函数在语法上和函数一样,但是别被它们弄混。他们实际上是伪装成函数的对象。如果你打印检查一个PHP闭包或者匿名函数的类型,你会发现它们都是Closure类的实例。Closure可以看作是同string和integer一样重要的数据类型。


创建


我们都知道PHP的闭包和函数看起来很像。当你像例子 2-19那样创建一个PHP闭包后,你就不会感到惊讶了。


例子 2-19 简单的闭包

<?php $closure = function ($name) {    return sprintf('Hello %s', $name);};echo $closure("Josh");// 输出 --> "Hello Josh"

就这么简单。例子 2-19创建了一个Closure对象并将它赋值给变量$closure。它看起来像一个标准的PHP函数:它使用了相同的语法、接收参数并且有返回值。但是它没有名字。


我们可以调用$closure变量,因为$closure的是一个闭包,Closure闭包对象都实现了\_invoke()这个魔术方法。在变量名跟着一对()符号时PHP会自动查找并调用__invoke()方法。


我通常使用PHP的闭包对象作为函数和方法的回调。很多PHP的函数都会使用回调函数,例如array_map()和preg_replace_callback()。这就像为PHP匿名函数量身定做的功能!记住,就像其它任何值一样,闭包可以像参数一样被传递给其它PHP函数。在例子 2-10中我使用一个闭包对象作为array_map()函数的回调参数。


例子 2-20 array_map闭包

<?php $numbersPlusOne = array_map(function ($number) {    return $number +1;}, [1,2,3]);print_r($numbersPlusOne);// 输出 --> [2,3,4]

看起来并不是那么让人印象深刻是吗?但是记住,在闭包功能出现之前要实现这样的功能,PHP开发者们并没有什么好的选择,他们只能创建一个具名函数并把函数名作为参数传递进去才行。这样做执行上会有些慢,最重要的是它分离了回调的实现和使用。老派的PHP开发者们使用下面的代码:


<?php // 具名回调的实现function incrementNumber ($number) {    return $number + 1;}// 具名回调的使用$numberPlusOne = array_map('incrementNumber', [1,2,3]);print_r($numberPlusOne);

上面的代码固然可以正常执行,但是却没有例子 2-20中的简洁。我们并不需要一个单独以incrementNumber()命名的一次性函数作为回调。使用闭包作为回调可以创建出更简练、可读性更强的代码。


附着状态


目前为止我们演示了如何使用无名(也叫匿名)函数作为回调使用。下面让我们研究一下如何使用PHP闭包附着和封装状态。JavaScript开发者们可能会对PHP的闭包产生困惑,因为PHP的闭包不会像JavaScript一样自动将应用程序的状态封装给闭包。相反,你必须手动的调用闭包对象的bintTo()方法或者use关键词将状态附着给一个PHP闭包。


通常我们都是使用use关键词来附着闭包状态,所以让我们先以此为例(例子 2-21)。当你使用use关键词将一个变量附着给一个闭包时,被附着的变量值将一直保持为变量被附着给闭包的那一刻的值。


例子 2-21 使用use关键词附着一个闭包状态

<?phpfunction enclosePerson($name) {    return function ($doCommand) use ($name) {        return sprintf('%s, %s', $name, $doCommand);    };}// 将字符串"Clay"封装进闭包$clay = enclosePerson('Clay');// 调用闭包echo $clay('get me sweet tea!');// 输出 --> "Clay, get me sweet tea!"

在例子 2-21中,具名函数enclosePerson()函数接收一个$name参数,返回一个封装了$name参数的闭包对象。即使闭包最终离开了enclosePerson()函数的作用域,但是返回的闭包对象$clay中仍然会保留$name参数被附着给闭包时的值。也就说,$name变量仍然存在在闭包中!


你可以使用use关键词给闭包传递多个参数。参数之间使用逗号分隔,就像你们平时使用PHP的函数或者方法的参数一样。


别忘了PHP闭包都是对象。每个闭包的实例都有其内部状态,我们可以像其他PHP对象那样使用$this关键词来获取这些状态。一个闭包对象的默认状态相当无趣,它包含了一个魔术方法__invoke()和一个bindTo()方法,仅此而已。


不过bindTo()方法却可以带领我们去发掘出一些有趣的实现。这个方法允许我们将闭包对象的内部状态绑定到另一个对象上。bindTo()方法的第二个参数相当关键,它可以指定闭包需要绑定到的对象的类。这样我们就可以在闭包中获取绑定后的对象中protected和private的成员变量了。


你会发现bindTo()方法经常被一些PHP框架用来将路由地址映射到匿名回调函数上。这些框架将一个匿名函数绑定到应用程序对象上。你可以在匿名函数中使用$this关键词来引用应用程序对象,就像例子 2-22中所示


例子 2-22 使用bindTo方法附着闭包状态

<?phpclass 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 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;    }}

注意addRoute方法。它接收一个路由地址(例如 /users/josh)和一个路由的回调。dispatch()方法接收一个当前HTTP请求地址并调用对应的路由回调。神奇的地方在第11行,我们将路由的回调绑定给了当前App类的实例。这样我们就可以创造一个可以操作App实例状态的回调函数了:

<?php $app = new App();$app->addRoute('/users/josh', function () {    $this->responseContentType = 'application/json;charset=utf8';    $this->responseBody = '{"name": "Josh"}';});$app->dispatch('/users/josh');


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn