如何使用 Closure?
在 PHP 4 最早稱 callback
,PHP 5.3 引入 closure
與 anonymous function
,PHP 5.4 則新增 callable
type hint。
所以在 PHP 中,callback
、closure
、anonymous function
,與 callable
,事實上指的是同一件事情,但因為底層用的都是 Closure
物件,通常統稱為 closure。
Version
PHP 4 callback
PHP 5.3 closure / anonymous function
PHP 5.4 callable
Callback
將函式名稱以字串方式傳入,透過 call_user_func()
或 call_user_func_array()
去執行該字串為名稱的函式,類似 C 語言以 function pointer 實現 callback。1 1本文強調 closure 基本語法,關於實務上運用,詳細請參考實務上如何活用 Closure?
最簡單的Callback1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function myCallback()
{
echo 'World';
}
function sayHello($callback)
{
echo 'Hello ';
call_user_func($callback);
}
sayHello('myCallback');
// Result:
// Hello World
第1行1
2
3
4function myCallback()
{
echo 'World';
}
我們自己寫的 callback,只是一個普通的函式。
第6行1
2
3
4
5function sayHello($callback)
{
echo 'Hello ';
call_user_func($callback);
}
我們傳入一個 callback 函式,2 2由於PHP是弱型別語言,只看到傳入$closure
很難讓我們知道到底傳入的是普通變數,或者真的是一個 callback,所以在 PHP 5.4 才有了 callable
type hint。希望在執行 echo 'Hello'
之後,能執行我們傳入的 $callback
,這時會用到 PHP 內建的 call_user_func()
來呼叫我們的傳入的 callback 函式。
12行1
sayHello('myCallback');
呼叫 sayHello()
,並傳入 myCallback
,注意這裏傳的是 callback 函式名稱的字串。3 3這個字串很容易讓我們誤解是普通字串,或者是真的是一個 callback 函式,所以在 PHP 5.3 讓我們可以直接傳入一個 anonymous function。
Anonymous Function
沒有名稱的函式,可被指定到變數,如同物件般傳遞到其他函式,通常使用在只使用一次的 callback,讓我們不必再為了只使用一次的 callback 定義函式。
最簡單的 Anonymous Function
1 | $anonyFunc = function ($name) { |
第1行1
2
3$anonyFunc = function ($name) {
return 'Hello ' . $name;
};
建立了 Closure
物件,並且指定給 $anoy_func
變數,它看起來像函式,卻是個物件,與函式相同語法,可接受參數,也可回傳參數,然而它卻沒有函式名稱。
Anonymous Function 內部實作
當我們使用 $anony_func
時,$anony_func
變數是一個 Closure
物件,它實作了 __invoke()
magic method,當物件加上 ()
後,PHP就會呼叫其 __invoke()
magic method 執行 anonymous function。
以上的範例,也可以改用 __invoke()
方式執行。
1 | $anonyFunc = function ($name) { |
將 Anonymous Function 用於 Callback
許多 PHP 內建的函式都需要傳入 callback,如 array_map()
。在沒有 anonymous function 之前,我們只能這樣子使用 array_map()
。
1 | // Named callback implementation |
array_map()
函式的第 1 個參數要求我們傳進一個 callback, 在 PHP 還沒有 anonymous function 之前,只能事先寫好一個函式,然後將函式名稱以字串的方式傳進 array_map()
第 1 個參數來實現 callback。4 4incrementNumber
是以字串的方式傳入,很容易誤會到底是字串還是 callback 函式,因此才有 PHP 5.3 的 anonymous function與 PHP 5.4 的 callable type hint。
這樣雖然可行,但通常 callback 只會用一次,為了這種只會用一次的函式另外獨立出一個函式似乎不妥,比較好的方式是改用 anonymous function。
1 | $numbersPlusOne = array_map(function ($number) { |
由於 callback 只使用一次,將 anonymous function 直接寫在 array_map()
參數內除了更簡潔外,也解決了 PHP 以字串傳入 callback 的問題,程式可讀性也更高。
Closure
會將外層變數複製進 closure 函式,儘管函式結束,一樣可以讀取或寫入該外層變數。
使用 use 關鍵字
Pass by Reference 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function func(val)
{
var foo = function () {
val++;
};
foo();
return val;
}
console.log(func(0));
// Result:
// 1
對於 JavaScript 而言,因為 foo
這個 closure 使用到外層的 val
變數,JavaScript 的 closure 會自動將外層的 val
變數以 pass by reference 方式傳進 closure,所以 val++
會直接影響到外變數 val
。
1 | function func($val) |
對於 PHP 而言,因為 $foo
這個 closure 使用到外層的 $val
變數,PHP 的 closure 不會自動將外層的 $val
變數傳進來,必須加上 use
關鍵字,並且將變數前面加上 &
以 pass by reference 方式傳進 closure,所以 $val++
才會直接影響到外變數 val
。
Pass by Value 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function func(val)
{
var foo = function () {
val2 = val;
val2++;
};
foo();
return val;
}
print(func(0));
// Result:
// 0
JavaScript 並沒有提供 pass by value 的好方法,所以只能自己使用類似 val2 = val
的蠢方法,避免影響到原來的 val
。
1 | function func($val) |
PHP 因為預設就是 pass by value,所以只要拿掉 &
,就是以 pass by value 的方式傳遞。5 5closure 還有一個進階的 bindTo()
用法,詳細請參考深入探討 bindTo()
- JavaScript 的 closure 可以直接使用外層變數,而 PHP 必須加上
use
,明確指定要使用哪一個變數。 - JavaScript 對於 closure 的外層變數是以 pass by reference 處理,而 PHP 是 pass by value,若要 pass by reference,必須手動加上
&
。
Callable
為了避免因為 PHP 的弱型別本質,讓我們誤會傳入的是一般型別的參數,PHP 特別為傳入的 callback 參數加上 callable
type hint,讓我們可以更明確得知要傳入的是 callback。
如PHP內建的 array_map()
就使用了 callable
type hint。
1 | array array_map ( callable $callback , array $array1 [, array $... ] ) |
最簡單的Callable 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15$myCallback = function () {
echo 'World';
};
function sayHello(callable $callback)
{
echo 'Hello ';
call_user_func($callback);
}
sayHello($myCallback);
// Result:
// Hello World
與 callback 的範例一樣,只是改寫成 callable。
第6行1
2
3
4
5function sayHello(callable $callback)
{
echo 'Hello ';
call_user_func($callback);
}
在 $callback
之前多加了 callable
type hint,這樣就能很清楚的了解要傳入的是一個 callback,而不是其他普通參數。
與其他程式語言比較
JavaScript 1
2
3
4
5
6
7
8
9
10
11
12function myClosure(hi)
{
return function(name) {
return hi + " " + name;
}
}
var foo = myClosure("Hello");
console.log(foo("World"));
// Result:
// Hello World
- 支援一級函式,函式可以指定給變數,函式可以直接傳遞至其他函式,也可以回傳函式。
- 自動捕捉外層變數。
php 1
2
3
4
5
6
7
8
9
10
11
12function myClosure($hi)
{
return function($name) use ($hi) {
return sprintf("%s, %s", $hi, $name);
};
}
$foo = myClosure("Hello");
echo($foo("World"));
// Result:
// Hello World
- 支援一級函式,函式可以指定給變數,函式可以直接傳遞至其他函式,也可以回傳函式。
- 使用 use 關鍵字捕捉外層變數。
Conclusion
- 以上雖然分別以 callback, anonymous funciton,closure 與 callable 說明,但實務上都是一起使用。
- Closure 的出現使的 PHP 原本需要使用 callback 的函式有更精簡的寫法。
- JavaScript 的 closure 不用使用
use
,會自動將外層變數以 pass by reference 傳入,但 PHP 需手動使用use
關鍵字指定欲傳入的外層變數,預設是 pass by value ,若要與 JavaScript 完全一樣,需加上&
以 pass by reference 傳入。