导航
导航
文章目录
  1. 一、闭包的基本用法
    1. 1.1 示例1:作为变量的值来使用
    2. 1.2 示例2:作为回调函数参数的值
    3. 1.3 示例3:从父作用域继承变量
  2. 二、Closure 闭包类
    1. 2.1 Closure::bindTo
    2. 2.2 Closure::bind
  3. 三、闭包的作用
    1. 3.1 读取局部变量
    2. 3.2 使用 bindTo() 或者 Closure::bind

PHP 匿名函数和闭包

匿名函数(Anonymous functions),也叫闭包函数(closures),允许临时创建一个没有指定名称的函数。闭包和匿名函数在 PHP5.3.0 中引入的。

闭包和匿名函数看起来像是函数,实际上他们是 Closure 类的一个实例,数据类型是对象。

参考链接:匿名函数

一、闭包的基本用法

1.1 示例1:作为变量的值来使用

闭包可以作为变量的值来使用,因为闭包实际是 PHP 内置类 Closure 的对象实例,数据类型是对象,因此我们可以把一个 closure 对象赋值给一个变量。

// 闭包赋值给 $clousre 变量
$clousre = function ($name) {
    return 'Hello ' . $name;
};

// 可以用"变量名+括号"的形式调用,是因为 Closure 类中有 __invoke() 魔术方法
// 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
// nesfo 会传递给 $name 变量
echo $closure('nesfo');

参考链接:

1.2 示例2:作为回调函数参数的值

闭包经常作为回调函数(callback)参数的值来使用

// 根据第一个参数给出的规则,使用一个回调函数替换第三个参数给出的内容。
// 注意回调函数的参数($match)是一个数组,其最后返回真正参与替换的字符串
// $match[1] 的值是 -w
echo preg_replace_callback('~-([a-z])~', function ($match) {
    // 将 -w 转换成大写 W
    return strtoupper($match[1]);
}, 'hello-world');
// 输出 helloWorld

参考链接:

1.3 示例3:从父作用域继承变量

闭包可以从父作用域中继承变量,但是需要使用 use 来传递进去。

$message = 'hello';

// 没有 "use"
$example = function () {
    var_dump($message);
};
echo $example();
//输出为null,匿名函数无法继承外部变量 $message,因为没有 "use"

使用 use 传递父作用域 $message 变量

// 继承 $message
$message = 'hello';

$example = function () use ($message) {
    var_dump($message);
};
echo $example();
//输出 string 'hello' (length=5),使用关键词 use 后,
//匿名函数可以获取到外部变量 $message.

引用继承,原理是匿名函数在定义时(不是在调用时)会获取其所继承的变量的值

$message = 'hello';
// 引用继承,注意 &message
$example = function () use (&message) {
    var_dump($message);
};
$message = 'world';
echo $example();
//输出 string 'world' (length=5),

二、Closure 闭包类

前面说过,闭包是 Closure 类的一个实例

Closure 类定义了三种方法:

  • Closure::__construct — 构造函数,定义了 Closure 类禁止实例化。
  • Closure::bindTo — 复制当前闭包对象内容,并给这个闭包绑定一个新的对象和类作用域
  • Closure::bind — 复制一个闭包对象内容,并给这个闭包绑定一个新的对象和类作用域

2.1 Closure::bindTo

复制当前闭包对象(创建并返回一个新的闭包),并给这个闭包绑定一个新的对象($newthis),并且可以为这个闭包指定类作用域($newscope)。

// newthis 绑定给匿名函数的一个对象,或者 NULL 来取消绑定。
// newscope 为这个新闭包指定类作用域,或者 'static' 保持当前状态。
// 返回新创建的 Closure 对象 或者在失败时返回 FALSE
public Closure Closure::bindTo ( object $newthis [, mixed $newscope = 'static' ] )

如果你只是想要复制一个闭包,不需要绑定新到对象,可以用 cloning 代替。

bindTo 示例:

class A {
    function __construct($val) {
        $this->val = $val;
    }
    function getClosure() {
        //returns closure bound to this object and scope
        return function() { return $this->val; };
    }
}

$ob1 = new A(1);
$ob2 = new A(2);

// 返回一个闭包对象,注意这里的 $this 指向的是 $ob1 对象
$cl = $ob1->getClosure();
echo $cl(), "\n"; //结果是 1

// 复制上面 $cl 这个闭包对象内容,将 $this 绑定到了 $ob2 对象
$cl = $cl->bindTo($ob2);
echo $cl(), "\n"; //结果是 2

参考链接:

2.2 Closure::bind

这个方法是 Closure::bindTo() 的静态版本。

语法结构:

public static Closure Closure::bind ( Closure $closure , object $newthis [, mixed $newscope = 'static' ] )
  • closure 需要绑定的匿名函数
  • newthis 需要绑定到匿名函数的对象,或者 NULL 创建未绑定的闭包
  • newscope 想要绑定给闭包的类作用域,或者 ‘static’ 表示不改变

bind 示例:

class A {
    private static $sfoo = 1;
    private $ifoo = 2;
}
// 注意 $cl1 是 static 静态版闭包
$cl1 = static function() {
    return A::$sfoo;
};
// $ifoo 是非静态属性,使用  $this->ifoo 访问
$cl2 = function() {
    return $this->ifoo;
};

// 复制 $cl1 闭包,不绑定新对象,作用域 class A
$bcl1 = Closure::bind($cl1, null, 'A');

// 复制 $cl2 闭包,绑定到 new A() 对象,作用域 class A
// 这里必须要为 $this 绑定到一个对象,而上面的 $cl1 是静态的,可以直接 A::$sfoo 访问,不需要绑定对象。
$bcl2 = Closure::bind($cl2, new A(), 'A');

echo $bcl1(), "\n"; // 1
echo $bcl2(), "\n"; // 2

参考链接:

三、闭包的作用

3.1 读取局部变量

通过闭包可以读取其他函数的内部变量

function f1() {
    $n = 2;
    // 通过闭包函数 $f2 使用 use 关键字获取外部变量 $n
    $f2 = function () use($n) {
        return $n;
    };
    return  $f2;
}
var_dump($n);
// null, 因为 $n 是函数内的局部变量

$f = f1();
var_dump($f());
//int 2

3.2 使用 bindTo() 或者 Closure::bind

bindTo 或者 Closure::bind 方法可以把闭包的 $this 绑定到其他对象上,这样闭包就可以访问那个对象中受保护和私有的成员变量。

class App
{
    protected $routes = [];
    protected $responseStatus = '200 OK';
    protected $responseContentType = 'text/html';
    protected $responseBody = 'Hello world';

    public function addRoute($routePath, $routeCallback){
        $this->routes[$routePath] = Closure::bind($routeCallback,$this, __CLASS__);
        //$this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__);
    }

    public function dispatch($currentPath){
        foreach($this->routes as $routePath => $callback){
            if ($routePath === $currentPath) {
                // 闭包中的 __invoke() 方法会被自动调用
                // $callback(5); 闭包可以加参数
                $callback();
            }
        }

        // $this->responseBody 原来值是 Hello world
        // 闭包绑定到 App 后,默认值修改为 {"name": "nesfo"}
        header('HTTP/1.1' . $this->responseStatus);
        header('Content-type: ' . $this->responseContentType);
        header('Content-length' . mb_strlen($this->responseBody));
        echo $this->responseBody;
    }
}

$app = new App();
$app->addRoute('/user/nesfo', function ($test) {
    $this->responseContentType = 'application/json; charset=utf8';
    $this->responseBody = '{"name": "nesfo"}';
    $this->test = $test;
});
$app->dispatch('/user/nesfo');

参考文档:

支持一下
扫一扫,支持一下