閉包 是 PHP 5.3 引入的革命性特性,將函數提升為「一等公民」。本文從零開始構建認知:匿名函數定義調用、use 關鍵字捕獲外部變量 (值 vs 引用)、閉包對象內部結構、Closure 類反射與綁定 API,並對比 JavaScript 閉包差異。
// 定義並賦值給變量 (必須有分號結尾)
$greet = function(string $name): string {
return "Hello, $name!";
};
// 直接調用
echo $greet('World'); // Hello, World!
// 作為回調傳遞
$numbers = [1, 2, 3, 4];
$squares = array_map(function($n) { return $n * $n; }, $numbers);
// [1, 4, 9, 16]
// 內聯調用
echo preg_replace_callback('~-([a-z])~', function($m) {
return strtoupper($m[1]);
}, 'hello-world'); // helloWorld
歷史對比:PHP 5.3 前僅有 create_function(),代碼需寫字符串,無語法高亮、難調試、性能差,現已移除 (7.2+)。
use 綁定外部變量閉包定義時捕獲外部作用域變量,形成封閉環境:
$message = 'Hello';
// 1. 值綁定 (默認) - 複製定義時的值
$closure1 = function() use ($message) {
echo $message;
};
$message = 'World';
$closure1(); // 輸出: Hello (捕獲的是定義時快照)
// 2. 引用綁定 (&) - 動態關聯變量
$message = 'Hello';
$closure2 = function() use (&$message) {
echo $message;
};
$message = 'World';
$closure2(); // 輸出: World (讀取最新值)
// 3. 修改外部變量 (需引用綁定)
$counter = 0;
$increment = function() use (&$counter) {
return ++$counter;
};
echo $increment(); // 1
echo $increment(); // 2
echo $counter; // 2 (外部變量被修改)
// 4. 多變量綁定
$x = 1; $y = 2;
$sum = function() use ($x, $y) { return $x + $y; };
echo $sum(); // 3
Closure 類PHP 內部將閉包表示為 Closure 實例,提供元編程能力:
$fn = function($a, $b = 10) { return $a + $b; };
// 1. 類型檢查
var_dump($fn instanceof Closure); // true
// 2. 反射分析參數
$ref = new ReflectionFunction($fn);
echo $ref->getNumberOfParameters(); // 2
echo $ref->getNumberOfRequiredParameters();// 1
foreach ($ref->getParameters() as $param) {
echo $param->getName() . ' - ' . ($param->isOptional() ? 'optional' : 'required') . PHP_EOL;
}
// 3. 調用方式
$fn(1, 2); // 直接調用
$fn->__invoke(1, 2); // 同義
$fn->call(null, 1, 2); // 綁定 $this (此處 null 表示靜態調用)
// 4. 綁定新的 $this 上下文 (進階)
class A { private $val = 100; }
class B { private $val = 200; }
$accessor = function() { return $this->val; };
echo $accessor->bindTo(new A(), A::class)(); // 100 (突破私有訪問!)
echo $accessor->bindTo(new B(), B::class)(); // 200
function createCounter(int $start = 0): Closure {
$count = $start;
return function() use (&$count) {
return $count++;
};
}
$counterA = createCounter(10);
$counterB = createCounter(100);
echo $counterA(); // 10
echo $counterA(); // 11
echo $counterB(); // 100
echo $counterB(); // 101
// 每個閉包擁有獨立的 $count 環境,互不干擾
function lazyLoad(string $key, Closure $factory) {
static $cache = [];
return $cache[$key] ??= $factory();
}
// 使用
$db = lazyLoad('db', function() {
return new PDO('sqlite::memory:');
});
$repo = lazyLoad('repo', function() use ($db) {
return new UserRepository($db);
});
| 特性 | PHP | JavaScript |
|---|---|---|
| 語法 | function() use ($x) {} | () => {} / function() {} |
| 變量捕獲 | 顯式 use (值/引用可選) | 隱式詞法作用域 (自動捕獲引用) |
| 修改外部變量 | 需 use (&$x) | 直接修改 (let/var) |
| this 綁定 | 動態 (bindTo/call) | 詞法 (箭頭函數) / 動態 (普通函數) |
| 序列化 | 不支援 (需第三方庫) | 不支援原生 |
| 性能 | 對象分配開銷較大 | 高度優化 (V8 內聯緩存) |
$obj->handler = fn() => $obj->do(); 導致 GC 無法回收,用 WeakReference (7.4+) 打破循環。for ($i=0;$i<3;$i++) $fns[] = fn() => $i; 全輸出 3,修正:fn() use ($i) => $i。opis/closure 或 superclosure/superclosure 庫。掌握 use 值/引用綁定語義與 Closure::bindTo 上下文切換,是寫出優雅、函數式風格 PHP 代碼的關鍵。