PHP 5.3 引入閉包,標誌著 PHP 邁向函數式程式設計的關鍵一步。本文從語法細節、變數綁定機制、Closure 類別方法、靜態閉包、bindTo 動態綁定,到實戰設計模式 (策略、中介軟體、延遲載入) 全面剖析。
<?php
// 1. 匿名函數賦值給變數
$greet = function (string $name) { return "Hello, $name"; };
echo $greet('World'); // Hello, World
// 2. 立即調用 (IIFE)
echo (function ($x) { return $x * 2; })(5); // 10
// 3. 箭頭函數 (PHP 7.4+)
$double = fn($x) => $x * 2;
<?php
$factor = 10;
$multiplier = function ($x) use ($factor) { return $x * $factor; };
echo $multiplier(3); // 30
// 值綁定 (預設):建立時複製值
$factor = 20;
echo $multiplier(3); // 仍為 30
// 參考綁定:使用 & 符號
$counter = 0;
$inc = function () use (&$counter) { return ++$counter; };
echo $inc(); // 1
echo $inc(); // 2
$counter = 100;
echo $inc(); // 101
| 方法 | 用途 |
|---|---|
bindTo($newThis, $newScope) | 複製閉包並綁定新的 $this 與作用域 (類別名或 'static') |
bind($closure, $newThis, $newScope) | 靜態方法,同上 |
call($newThis, ...$args) | 臨時綁定 $this 並立即調用 (PHP 7+) |
fromCallable($callable) | 從可呼叫單元建立閉包 (PHP 7.1+) |
<?php
class Service {
private $secret = 'internal-data';
public function getClosure() {
return function () { return $this->secret; };
}
}
$svc = new Service();
$closure = $svc->getClosure();
// 直接調用會報錯:Cannot access private property
// 使用 bindTo 綁定到 Service 實例
$bound = $closure->bindTo($svc, Service::class);
echo $bound(); // internal-data
<?php
$staticClosure = static function ($x) { return $x * 2; };
// 靜態閉包不綁定 $this,不捕獲外部變數,效能略優
// 適合作為純函數、回調、遞歸輔助
// 遞歸範例:需用 use (&$fn) 參考自身
$factorial = static function ($n) use (&$factorial) {
return $n <= 1 ? 1 : $n * $factorial($n - 1);
};
echo $factorial(5); // 120
<?php
interface DiscountStrategy { public function apply(float $price): float; }
$strategies = [
'vip' => fn($p) => $p * 0.8,
'vip2' => fn($p) => $p - 50,
'normal'=> fn($p) => $p,
];
function checkout(float $price, string $level, array $strategies): float {
return ($strategies[$level] ?? $strategies['normal'])($price);
}
echo checkout(200, 'vip', $strategies); // 160
<?php
type Middleware = callable(Request $req, callable $next): Response;
$pipeline = [
fn($req, $next) => { /* auth */ return $next($req); },
fn($req, $next) => { /* log */ return $next($req); },
fn($req, $next) => { /* cors */ return $next($req); },
];
function dispatch(Request $req, array $pipeline): Response {
$handler = fn($r) => new Response('Hello');
foreach (array_reverse($pipeline) as $mw) {
$handler = fn($r) => $mw($r, $handler);
}
return $handler($req);
}
<?php
$container = [];
$container['db'] = static function () {
return new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
};
// 首次使用時才建立連線
$db = $container['db']();
static fn 或具名函數。use (&$var) 參考可能導致記憶體洩漏 (循環引用),PHP 7.3+ 已改善但仍需注意。Closure::bindTo 每次呼叫產生新閉包對象,高頻場景請快取。閉包讓 PHP 擁頭等函數能力,配合類型系統 (PHP 7.4+ 箭頭函數、PHP 8.0+ 聯合類型、PHP 8.1+ never、readonly) ,現代 PHP 已能優雅表達函數式、聲明式邏輯,大幅減少樣板代碼並提升可測試性。