php的闭包特性

3 minute read

闭包和匿名函数在PHP 5.3.0引入,并且PHP将两者视为相同的概念。闭包其实是伪装成函数的对象,它的实质其实是Closure实例。

创建闭包非常简单:

1$c = function($name) {
2    return sprintf("Hello World! Hello %s!", $name);
3};
4
5echo $c('PHP');

使用use对闭包附加状态,多个参数使用,分隔:

1function callPerson($name) {
2    return function($about) use ($name) {
3        return sprintf("%s, %s", $name, $about);
4    }
5}
6
7$triver = callPerson('Triver');
8echo $triver("slow down, please!!");

附加的变量会被封装到闭包内,即使返回的闭包队形已经跳出了callPerson()的作用域也仍然会记住$name的值。

闭包有一个有趣的bindTo()方法,可以将闭包的内部状态绑定到其他对象上,第二个参数指定了绑定闭包的对象所属的类,从而实现在闭包中访问绑定对象的私有方法和属性。

 1class Bind {
 2    protected $name = 'no name';
 3    public $change;
 4
 5    public function addAction($action) {
 6        $this->change = $action->bindTo($this, __CLASS__);
 7    }
 8}
 9
10$bind = new Bind();
11$bind->addAction(function() {
12    $this->name = "php";
13    return $this->name;
14    });
15
16$change = $bind->change;
17echo $change();

使用这个特性可以方便的为类添加方法并绑定:

 1trait MetaTrait
 2{
 3    //定义$methods数组,用于保存方法(函数)的名字和地址。
 4    private $methods = array();
 5    //定义addMethod方法,使用闭包类绑定匿名函数。
 6    public function addMethod($methodName, $methodCallable)
 7    {
 8        if (!is_callable($methodCallable)) {
 9            throw new InvalidArgumentException('Second param must be callable');
10        }
11        $this->methods[$methodName] = Closure::bind($methodCallable, $this, get_class());
12    }
13    //方法重载。为了避免当调用的方法不存在时产生错误,
14    //可以使用 __call() 方法来避免。
15    public function __call($methodName, array $args)
16    {
17        if (isset($this->methods[$methodName])) {
18            return call_user_func_array($this->methods[$methodName], $args);
19        }
20
21        throw RunTimeException('There is no method with the given name to call');
22    }
23}
24
25class HackThursday {
26    use MetaTrait;
27
28    private $dayOfWeek = 'Thursday';
29
30}
31
32$test = new HackThursday();
33$test->addMethod('when', function () {
34    return $this->dayOfWeek;
35});
36
37echo $test->when();

php7 中增加了 Closure::call() 方法,可以更高效的绑定对象作用域并调用。

 1class A {private $x = 1;}
 2
 3// Pre PHP 7 code
 4$getXCB = function() {return $this->x;};
 5$getX = $getXCB->bindTo(new A, 'A'); // intermediate closure
 6echo $getX();
 7
 8// PHP 7+ code
 9$getX = function() {return $this->x;};
10echo $getX->call(new A);