学习PHP闭包(Closure)

在学习Laravel框架的时候遇到了“闭包”的概念,查PHP的手册及网上的解释,分析对比一下,试着去理解。

可能目前理解的还不妥,先记录下来吧。

一、闭包函数的基本用法

匿名函数(Anonymous functions),也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数。最经常用作回调函数(callback)参数的值。
Example #1 匿名函数示例,用作回调函数参数的值。

<?php
echo preg_replace_callback('~-([a-z])~', function ($match) {
    return strtoupper($match[1]);
}, 'hello-world');
// 输出 helloWorld
?>

闭包函数也可以作为变量的值来使用。PHP 会自动把此种表达式转换成内置类 Closure 的对象实例。把一个 closure 对象赋值给一个变量的方式与普通变量赋值的语法是一样的,最后也要加上分号:
Example #2 匿名函数变量赋值示例

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

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

闭包可以从父作用域中继承变量。 任何此类变量都应该用 use 语言结构传递进去。
Example #3 从父作用域继承变量

$message = 'hello';

// 没有 "use"
$example = function () {
    var_dump($message);
};
echo $example();
//输出为null,匿名函数继承(获取)到外部变量$message.
// 继承 $message
$message = 'hello';

$example = function () use ($message) {
    var_dump($message);
};
echo $example();
//输出 string 'hello' (length=5),使用关键词use后,
//匿名函数可以获取到外部变量$message.
// Inherited variable's value is from when the function
// is defined, not when called
// 继承的变量的值在匿名函数定义时获取,并不是在调用时。
// 可以了解这种通过变量名的继承,是一种复制的方式。
$message = 'hello';

$example = function () use ($message) {
    var_dump($message);
};

$message = 'world';
echo $example();
//输出仍然为 string 'hello' (length=5),
//就算调用前$message的值改变了。
// 引用继承
// Inherit by-reference

$message = 'hello';

$example = function () use (&message) {
    var_dump($message);
};

$message = 'world';
echo $example();
//输出 string 'world' (length=5),

Example #4 匿名函数可以接受参数

$message = 'world';
$example = function ($arg) use ($message) {
    var_dump($arg . ' ' . $message);
};
$example("hello");
//输出 string 'hello world' (length=11)

二、闭包 Closure 类

匿名函数目前是通过 Closure 类来实现的。
Closure类定义了三种方法:

  • Closure::__construct — 用于禁止实例化的构造函数。
  • Closure::bind — 复制一个闭包,绑定指定的$this对象和类作用域。
  • Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。

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

//public static Closure Closure::bind ( Closure $closure , 
//object $newthis [, mixed $newscope = 'static' ] )
class A {
    private static $sfoo = 1;
    private $ifoo = 2;
}
$cl1 = function() {
    return A::$sfoo;
};
$cl2 = function() {
    return $this->ifoo;
};

$bcl1 = Closure::bind($cl1, null, 'A');
$bcl2 = Closure::bind($cl2, new A(), 'A');
echo $bcl1(), "\n";
echo $bcl2(), "\n";
//输出
//1
//2

Closure::bindTo的用法:

//public Closure Closure::bindTo ( object $newthis [, 
//mixed $newscope = 'static' ] )
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);

$cl = $ob1->getClosure();
echo $cl(), "\n";
$cl = $cl->bindTo($ob2);
echo $cl(), "\n";

三、闭包的理解

1.变量的作用域

变量的作用域无非就是两种:全局变量和局部变量。

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。

如何从外部读取局部变量?看下面的例子:

function f1() {
        $n = 2;
        $f2 = function () use($n) {
            return $n;
        };
        return  $f2;
}
var_dump($n);
//null
$f = f1();
var_dump($f());
//int 2

在上面的代码中,匿名函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是选择可见的(关键词:use)。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

阮一峰老师从Javascriptd语言考虑,对闭包的理解的是:闭包就是能够读取其他函数内部变量的函数
他说,闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。 我对他的看法是认可的,但我觉着还不能够帮助我理解,因为虽然我知道闭包的作用是这样的,但然后呢?

我觉得,PHP的闭包的绑定作用,可以加深对闭包的应用的理解。

2.闭包的绑定

下面这个例子有点容器的影子。

只不过这个容器只是用来“装”函数的,Laravel的容器是用来“装”类的。

MetaTrait.php
<?php
//关键词:trait,可以理解为除继承、接口的另外一种解决“多重继承”方法。
trait MetaTrait
{
    //定义$methods数组,用于保存方法(函数)的名字和地址。
    private $methods = array();
    //定义addMethod方法,使用闭包类绑定匿名函数。
    public function addMethod($methodName, $methodCallable)
    {
        if (!is_callable($methodCallable)) {
            throw new InvalidArgumentException('Second param must 
                be callable');
        }
        $this->methods[$methodName] = Closure::bind($methodCallable, 
                $this, get_class());
    }
    //方法重载。为了避免当调用的方法不存在时产生错误,
    //可以使用 __call() 方法来避免。
    public function __call($methodName, array $args)
    {
        if (isset($this->methods[$methodName])) {
            return call_user_func_array($this->
                    methods[$methodName], $args);
        }

        throw RunTimeException('There is no method with 
                the given name to call');
    }

}
?>

test.php
<?php
require 'MetaTrait.php';
//定义HackThursday类,继承MetaTrait。
//可以把MetaTrait理解为容器的基类,HackThursday是一种容器,
//这个容器是装什么的呢?匿名函数。
//可以把匿名函数灵活地绑定至容器中。
class HackThursday {
    use MetaTrait;

    private $dayOfWeek = 'Thursday';

}

$test = new HackThursday();
$test->addMethod('when', function () {
    return $this->dayOfWeek;
});

echo $test->when();

?>

参考

1.学习Javascript闭包(Closure)
2.PHP手册-Closure类
3.PHP手册-匿名函数
4.Laravel学习笔记——神奇的服务容器