PHP Closure 類別完整解析:匿名函式、綁定作用域、__invoke 魔術方法
PHP 5.3 引入 Closure 類別代表匿名函式 (Anonymous Functions),是現代 PHP 函式式程式設計、依賴注入、中介軟體模式的基石。本文深入解析 Closure 內部結構、靜態綁定方法、動態作用域綁定、以及讓物件可呼叫的 __invoke 魔術方法。
Closure 類別核心特性
<?php
// 所有匿名函式都是 Closure 實例
$func = function() { echo 'hello'; };
var_dump($func); // object(Closure)#1 (0) { }
// 反射驗證:非介面、final 類別、內部類別
$ref = new ReflectionClass('Closure');
var_dump($ref->isInterface()); // false
var_dump($ref->isFinal()); // true
var_dump($ref->isInternal()); // true
三大核心靜態方法
| 方法 | 簽名 | 用途 |
Closure::bind() | bind(Closure $closure, $newThis, $newScope = "static") | 複製閉包並綁定新 $this 與類別作用域 |
Closure::bindTo() | bindTo($newThis, $newScope = "static") | 實例方法版,效果同 bind() |
Closure::call() | call($newThis, ...$args) | PHP 7.0+,直接呼叫並綁定 $this,不產生新閉包 |
作用域綁定實戰:存取私有屬性
<?php
class Service {
private $secret = 'API_KEY_12345';
protected $config = ['debug' => true];
public $name = 'MyService';
}
// 定義在類別外部的匿名函式
$accessor = function() {
return [
'secret' => $this->secret, // 私有屬性
'config' => $this->config, // 受保護屬性
'name' => $this->name, // 公開屬性
];
};
// 綁定到 Service 實例與作用域
$bound = Closure::bind($accessor, new Service(), 'Service');
$result = $bound();
print_r($result);
/* 輸出:
Array
(
[secret] => API_KEY_12345
[config] => Array ( [debug] => 1 )
[name] => MyService
)
*/
進階應用:建構函式注入與延遲執行
<?php
class Container {
private $bindings = [];
public function bind($abstract, $concrete) {
$this->bindings[$abstract] = $concrete;
}
public function make($abstract) {
$concrete = $this->bindings[$abstract];
if ($concrete instanceof Closure) {
// 綁定容器實例為 $this,使閉包能存取容器其他服務
return $concrete->bindTo($this, $this)();
}
return new $concrete;
}
}
// 使用
$container = new Container();
$container->bind('logger', function() {
return new Monolog\Logger('app');
});
$container->bind('db', function() {
// 這裡的 $this 是 Container 實例
return new PDO($this->make('config')->db_dsn);
});
$logger = $container->make('logger');
$db = $container->make('db');
__invoke():讓物件像函式一樣被呼叫
<?php
class Invokable {
private $prefix;
public function __construct($prefix) {
$this->prefix = $prefix;
}
public function __invoke($suffix) {
return $this->prefix . ' ' . $suffix;
}
}
$greeter = new Invokable('Hello');
echo $greeter('World'); // 輸出:Hello World
// 實際應用:可呼叫的中介軟體
class LoggerMiddleware {
public function __invoke($request, $next) {
$start = microtime(true);
$response = $next($request);
$duration = microtime(true) - $start;
error_log("{$request->method} {$request->uri} - {$duration}s");
return $response;
}
}
// 路由器中使用
$middleware = new LoggerMiddleware();
$response = $middleware($request, $handler);
Closure 與傳統函式/類別比較
| 特性 | 一般函式 | 類別方法 | Closure |
| 可攜帶狀態 (閉包) | ❌ | ✅ (透過 $this) | ✅ (use 關鍵字) |
| 動態建立 | ❌ | ❌ | ✅ |
| 可當作參數傳遞 | ✅ (字串名稱) | ✅ (callable) | ✅ (直接傳遞) |
| 序列化 | ❌ | ❌ | ✅ (PHP 7.0+) |
| 綁定作用域 | ❌ | 固定 | ✅ (bind/bindTo/call) |
| 效能 | 最快 | 快 | 略慢 (建立開銷) |
use 關鍵字:捕獲外部變數
<?php
$factor = 10;
$multiplier = function($x) use ($factor) {
return $x * $factor;
};
echo $multiplier(5); // 50
// 參考捕獲 (PHP 7.1+)
$counter = 0;
$increment = function() use (&$counter) {
return ++$counter;
};
echo $increment(); // 1
echo $increment(); // 2
echo $counter; // 2 (外部變數也被修改)
實際專案應用場景
- 路由器/中介軟體:Laravel/Symfony 路由閉包、中介軟體管道
- 依賴注入容器:延遲實例化、工廠閉包
- 事件監聽器:一次性回調、狀態捕獲
- 集合操作:
array_map/filter/reduce 搭配箭頭函式 (PHP 7.4+ fn())
- 異步程式設計:ReactPHP/Amp Promise 回調
效能與記憶體注意事項
- 閉包會捕獲外部變數,導致被捕獲變數無法被 GC 回收,長期運行程序需注意記憶體洩漏。
- 大量建立閉包 (如迴圈中) 有額外記憶體開銷,熱路徑優先用一般函式或靜態方法。
Closure::bind() 建立新閉包實例,Closure::call() 直接呼叫不建立新實例,效能較好。