PHP 中,Closure 又稱為 Anonymous Function,中文譯為匿名函式:從字面上的意思上推敲,就是「沒有名字的函式」。
$inc = function (int $n): int {
return $n + 1;
}
$inc(10); // 11
通常 Closure 用於「具有變化可能性」的函式參數,舉例來說在各 Routing 的實作中,時常可以看到以下用法
<?php
Route::get('/', function () { return 'index'; });
Route::get('/{name}', function ($name) { return "Hello, $name"; };
此處借鑑了 Laravel 的 Routing 寫法:第一個參數為 Path,第二個參數為這個 Path 的處理方法。
如果要將外部變數傳給 Closure,需要以 use
明確指定,且 Closure 中的變更不會影響外部變數:
$number = 10;
$c = function () use ($number) {
return ++$n;
};
$c(); // 11
$number; // 10
如果希望 Closure 中的變更影響外部變數,可以利用 &
將匿名函式中的變更影響原本的外部變數
$number = 10;
$c = function () use (&$number) {
return ++$n;
};
$c(); // 11
$number; // 11
PHP 在處理 Closure 時,是產生一個 Closure
Class,這個 Class 有一些特質
__consturct
為私有,故此 Class 無法被 new 出來bind
及 bindTo
兩個 methods,兩個功能類似但 bind
是 static__invoke
method,但僅是為了與 Callable Object 保持一致性,與 Closure 的執行無關bind
與 bindTo
bindTo
用於複製一個 Closure,並且使用原本的 $this
及其可見性。
假設存在 Class A:
<?php
class A
{
protected $val;
public function __construct($val) {
$this->val = $val;
}
public function getClosure(): Closure {
return function () {
return $this->val;
};
}
}
我們可以用以下方式利用 getClosure()
<?php
$firstClass = new A(10);
$secondClass = new A(20);
$aClosure = $firstClass->getClosure();
$aClosure(); // 10
$bClosure = $aClosure->bindTo($secondClass);
$bClosure(); // 20
因為 $aClosure
是從 $firstClass
中出來的,所以回傳 10
;因為 $bClosure
是將 $aClosure
這個 Closure 重新綁定到 $secondClass
,所以會回傳 20
。
順帶一提,bindTo
可以改變 method 的可見性,舉例來說
<?php
class A
{
protected $val = 10;
public function getClosure() {
return function () {
return $this->val;
};
}
}
class B
{
protected $val = 20;
}
$a = new A();
$b = new B();
$aClosure = $a->getClosure();
$aClosure(); // 10
// 在這邊將 $bClosure 的可見性設為 B Class
// 如此一來,$bClosure 就可以存取 B 的 protected variable
$bClosure = $aClosure->bindTo($b, B::class);
$bClosure(); // 20
bind
為 bindTo
的靜態版本,其用法基本上與 bindTo
類似,差別僅在於 Closure 可以在 bind
的第一個參數中指定
class A
{
protected $val = 10;
}
Closure::bind(function () {
return $this->val;
}, new A(), A::class);
bind
及 bindTo
不應是用來「複製」 Closure 使用,如果單純只是要複製一個 Closure 的話應該使用 clone
$c = function (int $n): int { return $n + 1; }
$cCopy = clone $c;
在 PHP 7.4 中,Arrow Functino 將簡化 Closure 的寫法。
$c = fn($x) => ++$x;
// $c = function ($x) { return $++x; }
另外,Arrow Function 會自動捕獲外部變數,所以不再需要使用 use
$y = 10;
$c = fn($x) => $x + $y;
$c(100); // 110
今天的內容針對 Closure 做了比較詳細的解釋。
事實上,在部份的 Framework 中已經大量使用 Closure 實現類似 Functional Programming 的 Lambda,雖然在目前(PHP 7.3)中還是有所不便,但這樣的狀況在 Arrow Function 出現後應該會有所改善。