PHP 闭包

一、闭包总结

把一个闭包转换为某个类的方法 (只是这个方法不需要通过对象调用), 这样闭包中的 $this、static、self 就转换成了对应的对象或类

把闭包当成对象的成员方法或者静态成员方法.

Closure::bind($cl1, null, 'A'); //就相当于在类里面加了个静态成员方法
Closure::bind($cl2, new A(), 'A'); //相当于在类里面加了个成员方法

成员方法中使用 $this 访问对象,静态成员方法直接使用类名::成员的方法.\
但是因为是匿名函数,没有函数名,所以返回一个已经绑定 $this 对象和类作用域的闭包给你使用.

二、闭包基本用法

闭包(Closure)又叫做匿名函数,也就是没有定义名字的函数。比如下面的例子:

// 定义一个闭包,并把它赋给变量 $f
$f = function () {
    return 7;
}

// 使用闭包也很简单
$f(); //这样就调用了闭包,输出 7

// 当然更多的时候是把闭包作为参数(回调函数)传递给函数
function testClosure (Closure $callback) {
    return $callback();
}

// $f 作为参数传递给函数 testClosure,如果是普遍函数是没有办法作为testClosure的参数的
testClosure($f);

// 也可以直接将定义的闭包作为参数传递,而不用提前赋给变量
testClosure (function () {
    return 7;
});

// 闭包不止可以做函数的参数,也可以作为函数的返回值
function getClosure () {
    return function () { return 7; };
}

$c = getClosure(); // 函数返回的闭包就复制给 $c 了
$c(); // 调用闭包,返回 7

三、闭包类(Closure)

定义一个闭包函数,其实是产生了一个闭包类(Closure)的对象,Closure 类摘要如下

Closure {   
    public static Closure bind (Closure $closure , object $newthis [, mixed $newscope = 'static' ])  
    public Closure bindTo (object $newthis [, mixed $newscope = 'static' ])  
} 

方法说明:\
Closure::bind: 复制一个闭包,绑定指定的 $this 对象和类作用域。\
Closure::bindTo: 复制当前闭包对象,绑定指定的 $this 对象和类作用域。\
下面将介绍 Closure::bind 和 Closure::bindTo\
参数和返回值说明:\
closure:表示需要绑定的闭包对象。\
newthis:表示需要绑定到闭包对象的对象,或者 NULL 创建未绑定的闭包。\
newscope:表示想要绑定给闭包的类作用域,可以传入类名或类的示例,默认值是'static', 表示不改变。\
该方法成功时返回一个新的 Closure 对象,失败时返回 FALSE。

class Animal {  
    private static $cat = "cat";  
    private $dog = "dog";  
    public $pig = "pig";  
}  

/*  
 * 获取Animal类静态私有成员属性 
 */  
$cat = static function() {  
    return Animal::$cat;  
};  

/*  
 * 获取Animal实例私有成员属性 
 */  
$dog = function() {  
    return $this->dog;  
};  

/*  
 * 获取Animal实例公有成员属性 
 */  
$pig = function() {  
    return $this->pig;  
};  

$bindCat = Closure::bind($cat, null, new Animal());// 给闭包绑定了Animal实例的作用域,但未给闭包绑定$this对象  
$bindDog = Closure::bind($dog, new Animal(), 'Animal');// 给闭包绑定了Animal类的作用域,同时将Animal实例对象作为$this对象绑定给闭包  
$bindPig = Closure::bind($pig, new Animal());// 将Animal实例对象作为$this对象绑定给闭包,保留闭包原有作用域  
echo $bindCat(),'<br>';// 根据绑定规则,允许闭包通过作用域限定操作符获取Animal类静态私有成员属性  
echo $bindDog(),'<br>';// 根据绑定规则,允许闭包通过绑定的$this对象(Animal实例对象)获取Animal实例私有成员属性  
echo $bindPig(),'<br>';// 根据绑定规则,允许闭包通过绑定的$this对象获取Animal实例公有成员属性

// bindTo与bind类似,是面向对象的调用方式,这里只举一个,其他类比就可以
$bindCat = $cat->bindTo(null, 'Animal');

以上示例输出:

cat
dog
pig

四、传递参数:use

闭包可以保存所在代码块上下文的一些变量和值。PHP 在默认情况下,匿名函数不能调用所在代码块的上下文变量,而需要通过使用 use 关键字。

function getMoney() {
    $rmb = 1;
    $dollar = 6;
    $func = function() use ( $rmb ) {
        echo $rmb;
        echo $dollar;
    };
    $func();
}
getMoney();
//输出:
//1
//报错,找不到dorllar变量

可以看到,dollar 没有在 use 关键字中声明,在这个匿名函数里也就不能获取到它,所以开发中要注意这个问题。

有人可能会想到,是否可以在匿名函数中改变上下文的变量,但我发现是不可以的:

function getMoney() {
    $rmb = 1;
    $func = function() use ( $rmb ) {
        echo $rmb;
        //把$rmb的值加1
        $rmb++;
    };
    $func();
    echo $rmb;
}
getMoney();
//输出:
//1
//1

原来 use 所引用的也只不过是变量的一个副本而已。但是我想要完全引用变量,而不是复制。要达到这种效果,其实在变量前加一个 & 符号就可以了:

function getMoneyFunc() {
    $rmb = 1;
    $func = function() use ( &$rmb ) {
        echo $rmb;
        //把$rmb的值加1
        $rmb++;
    };
    return $func;
}
$getMoney = getMoneyFunc();
$getMoney();
$getMoney();
$getMoney();
//输出:
//1
//2
//3

五、闭包的几种情况

bind 是 bindTo 的静态版本,因此只说 bind 吧。(还不是太了解为什么要弄出两个版本)\
下面详细讲解这几种情况:

5.1 只绑定 $this 对象

$closure = function ($name, $age) {
    $this->name = $name;
    $this->age = $age;
};

class Person {
    public $name;
    public $age;

    public function say() {
        echo "My name is {$this->name}, I'm {$this->age} years old.\n";
    }
}

$person = new Person();

//把$closure中的$this绑定为$person
//这样在$bound_closure中设置name和age的时候实际上是设置$person的name和age
//也就是绑定了指定的$this对象($person)
$bound_closure = Closure::bind($closure, $person);

$bound_closure('php', 100);
$person->say();

输出

1
My name is php, I’m 100 years old.

注意:在上面的这个例子中,是不可以在 $closure 中 使用 static 的,如果需要使用 static ,通过第三个参数传入带命名空间的类名。

5.2 只绑定类作用域.

$closure = function ($name, $age) {
  static::$name =  $name;
  static::$age = $age;
};

class Person {
    static $name;
    static $age;

    public static function say()
    {
        echo "My name is " . static::$name . ", I'm " . static::$age. " years old.\n";
    }
}

//把$closure中的static绑定为Person类
//这样在$bound_closure中设置name和age的时候实际上是设置Person的name和age
//也就是绑定了指定的static(Person)
$bound_closure = Closure::bind($closure, null, Person::class);

$bound_closure('php', 100);

Person::say();

输出

1
My name is php, I’m 100 years old.

注意:在上面的例子中,是不可以在 $closure 中使用 closure 中使用 $this 的,因为我们的 bind 只绑定了类名,也就是 static ,如果需要使用 $this,新建一个对象作为 bind 的第二个参数传入。

5.3 同时绑定 $this 对象和类作用域.(文档的说法)

$closure = function ($name, $age, $sex) {
    $this->name = $name;
    $this->age = $age;
    static::$sex = $sex;
};

class Person {
    public $name;
    public $age;

    static $sex;

    public function say()
    {
        echo "My name is {$this->name}, I'm {$this->age} years old.\n";
        echo "Sex: " . static::$sex . ".\n";
    }
}

$person = new Person();

//把$closure中的static绑定为Person类, $this绑定为$person对象
$bound_closure = Closure::bind($closure, $person, Person::class);
$bound_closure('php', 100, 'female');

$person->say();

输出

My name is php, I’m 100 years old. Sex: female.

在这个例子中可以在 $closure 中同时使用 $closure 中同时使用 $this 和 static

5.4 都不绑定

这样一来只是纯粹的复制,文档说法是使用 cloning 代替 bind 或 bindTo

$closure = function () {
    echo "bind nothing.\n";
};

//与$bound_closure = clone $closure;的效果一样
$bound_closure = Closure::bind($closure, null);

$bound_closure();

输出

1
bind nothing.
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 1

例子中的代码应该没有运行过吧

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!