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

  我们要特别注意addRoute()方法。这个方法的参数分别是一个路由路径(如/users/martini)和一个路由回调。dispatch()方法的参数是当前HTTP请求的路径,它会调用匹配的路由回调。addRoute()方法是重点所在,我们把路由回调绑定到了当前的App实例上,这样做能在回调函数中处理App实例的状态:

<?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"}

 

posted @ 2019-05-23 09:35  Martini  阅读(235)  评论(0编辑  收藏  举报