概述

闭包是指在创建时封装周围状态的函数,即使闭包所在的环境的不存在了,闭包中封装的状态依然存在。 匿名函数其实就是没有名称的函数,匿名函数可以赋值给变量,还能像其他任何PHP函数对象那样传递。不过匿名函数仍然是函数,因此可以调用,还可以传入参数,适合作为函数或方法的回调。

但在 php 中,闭包与匿名函数被视为相同的概念。

Closure 是代表闭包(匿名函数)的预定义类,该类定义了闭包的一些操作,例如bindbindTo等方法。

类定义:

1
2
3
4
5
6
7
Closure {
    private __construct ( void )
    public static bind ( Closure $closure , object $newthis [, mixed $newscope = "static" ] ) : Closure
    public bindTo ( object $newthis [, mixed $newscope = "static" ] ) : Closure
    public call ( object $newthis [, mixed $... ] ) : mixed
    public static fromCallable ( callable $callable ) : Closure
}

bindTo、bind 方法

bindTo,非静态方法,复制当前闭包对象,绑定到新的类对象、类作用域,并返回新的闭包。 bind,静态方法。复制一个闭包,绑定指定的类对象和类作用域,并返回绑定好的闭包。

解释两个参数:newthisnewscope

newthis

新的 this 对象。若想将闭包绑定到某个对象上,需指定此参数。否则传 null。 经常可以看到一些框架的路由定义如下:

1
2
3
$app->get('/test', function () {
    echo $this->request->getMethod();
});

在闭包中可以使用 this 关键字? 这是因为该闭包被绑定了 this 对象。绑定 this 对象后,就可以访问该对象的 public 属性、方法。若要访问非 public 方法、属性,则需绑定作用域。

newscope

新的作用域。当访问非 public 的方法、属性时,需指定作用域。 绑定新作用域的作用如下例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php

class Container
{
    protected static $name = 'container';
}

$clo1 = function() {
    var_dump(Container::$name);
};

$clo2 = Closure::bind($clo1,null,Container::class);
$clo1();
$clo2();

输出的接口中,$clo1()可以输出 name 静态变量的值,而$clo2()却直接报错。 这是因为 $clo1 的作用域与 container 中的 name 作用域不同,且 name 又是一个非 public 变量。因此 $clo 闭包中访问 Container::$name 就像直接从类外访问 name,肯定会报错的。 而 $clo2 通过 bind 绑定了 container 类作用域,$clo2 中访问 name 就像在 container 类中访问 name

因此,若想访问某个对象的非静态非公有属性、方法,则需绑定 newthisnewscope,代码示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php

class Test
{
    protected function getName()
    {
        return 'name';
    }
}

$clo = function() {
    var_dump($this->getName());
};

Closure::bind($clo,new Test(), Test::class)();  // 输出 name
Closure::bind($clo,new Test())();               // 报错:Fatal error: Uncaught Error: Call to protected method Test::getName() from context 'Closure'

bindTo、bind 方法的区别

bind 相当于 bindTo 的静态方法版,实现的功能上是一样的。

call 方法

1
$clo->call($obj, ...$params);

等价于

1
2
$clo->bindTo($obj, $obj);
$clo(...$params);

验证示例(改编自 PHP 官网):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class Value {
    protected $value;

    public function __construct($value) {
        $this->value = $value;
    }

    private function getValue() {
        return $this->value;
    }
}

$three = new Value(3);
$four = new Value(4);

$closure = function ($delta) { 
    var_dump($this->getValue() + $delta); 
};
$closure->call($three, 4);
$closure->call($four, 4);

fromCallable 方法

callable 类型的东西变成闭包,如果传入的参数不是 callable 的,则报错。 例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
class Foo
{
    protected $num = 1;

    public static function hello(string $bar)
    {
        echo 'hello ' . $bar;
    }
}

$hello = Closure::fromCallable(['Foo', 'hello']);
$hello('world');
Closure::fromCallable(['Foo', 'a']);  // 报错

总结

个人感觉闭包和类都挺像的,都是为了复用。只不过实现的角度不同,一个是面向对象,一个是面向过程的。

参考资料:

https://juejin.im/post/5b8129aa51882542f03807a8

https://stackoverflow.com/questions/57325088/difference-between-calling-a-closure-in-class-via-call-user-func-and-call