在編寫高階函數、策略模式、事件監聽器時,常需接受回調函數作為參數。傳統寫法無法在調用前驗證可調用性,導致運行時拋出難以追蹤的 Warning 或 TypeError。
// 傳統寫法:隱患重重
function doSomething($callback) {
// 這裡無法保證 $callback 可調用
call_user_func($callback); // 可能拋出 Warning
}
doSomething('non_existent_function'); // 執行到內部才報錯
PHP 5.4+ 引入 callable 類型提示,將驗證前置到參數綁定階段:
// 現代寫法:攔截器模式
function doSomething(callable $callback) {
// 進入函數體時,$callback 必定可調用
return call_user_func($callback);
}
// 正確調用
doSomething(function() { echo "Lambda\n"; });
doSomething('strlen'); // 內建函數
doSomething([$obj, 'method']); // 實例方法
doSomething(['ClassName', 'staticMethod']); // 靜態方法
doSomething('ClassName::staticMethod'); // PHP 7.1+ 字符串靜態調用
// 錯誤調用:直接在調用處拋出 TypeError
doSomething('abc');
// TypeError: Argument 1 passed to doSomething() must be callable, string given
| 類型 | 示例 | 說明 |
|---|---|---|
| 匿名函數 | fn() => 1 / function() {} | 最常用,支援閉包綁定 |
| 函數名字符串 | 'strlen' | 全局函數,不支援命名空間函數需帶前導 \ |
| 實例方法 | [$obj, 'method'] | $obj 必須是實例化對象 |
| 靜態方法(陣列) | ['Class', 'method'] | 類名區分大小寫 |
| 靜態方法(字符串) | 'Class::method' | PHP 7.1+,推薦此格式 |
| 可調用對象 | new class { function __invoke() {} } | 實現 __invoke() 魔術方法 |
若無法使用類型聲明(如動態參數),可手動檢查:
function safeCall($callback, ...$args) {
if (!is_callable($callback)) {
throw new InvalidArgumentException('參數必須可調用');
}
return call_user_func_array($callback, $args);
}
// 獲取可調用名稱(調試用)
$callback = [$obj, 'method'];
echo is_callable($callback, true); // 輸出 "ClassName::method"
interface PaymentGateway {
public function charge(float $amount): bool;
}
class PaymentProcessor {
private callable $strategy;
public function __construct(callable $strategy) {
$this->strategy = $strategy;
}
public function process(float $amount): bool {
// 編譯期/調用期雙重保障策略可執行
return ($this->strategy)($amount);
}
}
// 不同策略注入
$processor = new PaymentProcessor(fn($amt) => Stripe::charge($amt));
$processor = new PaymentProcessor([new Alipay(), 'charge']);
$processor = new PaymentProcessor('BitcoinGateway::process');
'Namespace\func' 需帶前導反斜杠 '\Namespace\func' 或使用 ['Namespace\Class', 'method']is_callable() 返回 true 但實際不可調用,需在類內部檢查或使用 ReflectionMethod::isPublic()declare(strict_types=1) 影響標量類型,但不影響 callable 行為is_callable() 更高效callable 類型聲明是 PHP 類型系統中被低估的利器。它將「可調用性」契約前置到類型系統層面,實現 失敗快速、靜態分析友好、文檔自說明 的三重收益。現代 PHP 代碼庫中,所有接受回調的函數/方法參數 強烈建議 加上 callable 聲明。