缘由

这几天看了Martin Sikora写的《PHP Reactive Programming》的第一章Introduction to Reactive Programming,其中对函数式编程举例时的PHP代码把匿名函数玩出了花,所以下午看了下关于Closure的文档

函数式编程

函数式编程(Functional Programming)是一种编程范式(Programming Paradigm)。

其要点有三:

  • 消除函数副作用(Eliminating side effects)

由于非局部变量改变或跳出函数体的控制语句,而造成的函数*变量不满足交换率的作用。

  • 变量不变性(Avoiding mutable data)

不改变非局部变量的状态,并且对于同样的输入,会造成相同的输出。

  • 函数作为程序基本数据类型(First-class citizens and higher-order functions)

函数可以作为函数参数、被赋值和作为函数返回值。

PHP匿名函数(Closure)

当我们提起PHP匿名函数的时候,一般指的是下面这样的代码。

<?php
$greet = function($name)
{
    printf("Hello %s\r\n", $name);
};

$greet('World');
$greet('PHP');

其中,$greet其实是一个Closure对象。

文档中对Closure类进行了详细的描述,PHP 7.1版本之前,主要包含三个方法bind,bindTo,call,这三个方法的区别主要是在于匿名函数对象中$this的使用。

<?php
class Closure {
    /* Methods */
    private __construct ( void )
    public static Closure bind ( Closure $closure , object $newthis [, mixed $newscope = "static" ] )
    public Closure bindTo ( object $newthis [, mixed $newscope = "static" ] )
    public mixed call ( object $newthis [, mixed $... ] )
    public static Closure fromCallable ( callable $callable )
}

bind/bindTo

两个方法实现的功能类似,都是复制一个匿名函数对象,指定其$this对象和类作用域。

以下仅就非静态方法bindTo进行说明。

$newthis指的是新的匿名函数对象中$this所调用的对象,而$newscope则会确定$this中成员的可见性。

默认参数static

使用默认参数static的情况如下:

<?php

class Foo {
    private $_foo = 1;
}

class Baz extends Foo {
    private $_baz = 2;
}

$func = function() {
    var_dump($this->_foo);
    var_dump($this->_baz);
};

$funcFoo = $func->bindTo(new Foo(), 'static');
$funcFoo();

这时候报错PHP Fatal error: Cannot access private property Foo::$_foo ,显然static无法访问private属性_foo

类名或者对象

如果我们指定Baz对象为$this,并将可见性设置为Baz的可见性。

<?php

class Foo {
    private $_foo = 1;
}

class Baz extends Foo {
    private $_baz = 2;
}

$func = function() {
    var_dump($this->_foo);
    var_dump($this->_baz);
};

$funcBaz = $func->bindTo(new Baz(), Baz::class);
$funcBaz();

这时候测试通过,新的$funcBaz中的$this可以访问Foo和Baz的私有属性。

当我们指定Baz对象为$this,而将可见性设置为Foo的可见性时。

<?php

class Foo {
    private $_foo = 1;
}

class Baz extends Foo {
    private $_baz = 2;
}

$func = function() {
    var_dump($this->_foo);
    var_dump($this->_baz);
};

$funcBaz = $func->bindTo(new Baz(), Foo::class);
$funcBaz();

程序会报错PHP Fatal error: Cannot access private property Baz::$_baz,这时候就无法访问Baz的私有属性了。

call

这个方法临时指定一个对象作为$this,并用剩余参数调用匿名函数,返回值为匿名函数的返回值。

<?php

class Foo {
    private $val;
    public function __construct($val) {
        $this->val = $val;
    }
    public function getVal() {
        return $this->val;
    }
}

$func = function() {
    var_dump($this->getVal());
};

$func->call(new Foo(1));// int(1)
$func->call(new Foo(2));// int(2)

其他

这个类在PHP版本升级7之后有一些变动:

  • $newscope在7.0.0版本后不能使用PHP内置的类
  • 7.1.0版本后新增加Closure::fromCallable ( callable $callable )方法,用以从callable构造一个匿名函数并检查,如果不可call,则会抛出TypeError