PHP 閉包

myjob發表於2019-05-31

一、閉包總結

把一個閉包轉換為某個類的方法(只是這個方法不需要通過物件呼叫), 這樣閉包中的$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

五、閉包的幾種情況

bindbindTo的靜態版本,因此只說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 中同時使用 $thisstatic

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.

相關文章