PHP 支援變數函數 ($func())、變數類別 (new $class())、靜態方法動態呼叫 ($class::$method()) 等特性,極大提升靈活性 (路由分發、插件系統、策略模式),但若直接暴露用戶輸入 ($_GET['action']) 將導致遠端程式碼執行 (RCE) 風險。本文系統梳理動態呼叫機制、實戰模式與安全加固。
| 語法 | 說明 | 範例 |
|---|---|---|
$func() | 變數函數 | $f='strlen'; $f('abc') |
$obj->$method() | 變數方法 | $m='getName'; $obj->$m() |
$class::$method() | 變數靜態方法 | User::${'get'.ucfirst($type)}() |
new $class() | 變數實例化 | $c='User'; new $c() |
call_user_func($callable, ...) | 統一呼叫介面 | call_user_func(['User','getAll']) |
call_user_func_array($callable, $args) | 陣列參數呼叫 | call_user_func_array($fn, $args) |
$callable(...) | PHP 7.1+ 直接呼叫 callable | $fn = 'strlen'; $fn('abc') |
<?php
// 安全版:白名單 + 命名空間映射
class Dispatcher {
private array $routes = [
'home' => ['controller' => \App\Controller\HomeController::class, 'method' => 'index'],
'user.list' => ['controller' => \App\Controller\UserController::class, 'method' => 'list'],
'user.view' => ['controller' => \App\Controller\UserController::class, 'method' => 'view'],
];
public function dispatch(string $action): void {
$route = $this->routes[$action] ?? null;
if (!$route) {
http_response_code(404);
echo 'Not Found';
return;
}
$controller = new $route['controller'](); // 變數實例化
$method = $route['method'];
if (!method_exists($controller, $method)) {
http_response_code(500);
echo 'Internal Error';
return;
}
// 依賴注入容器可在這裡注入 request/response
$controller->$method(); // 變數方法呼叫
}
}
// 使用
$action = $_GET['action'] ?? 'home';
(new Dispatcher())->dispatch($action);
<?php
interface PaymentStrategy {
public function pay(float $amount): bool;
}
class AlipayStrategy implements PaymentStrategy { public function pay(float $amount): bool { /* ... */ } }
class WechatStrategy implements PaymentStrategy { public function pay(float $amount): bool { /* ... */ } }
class UnionPayStrategy implements PaymentStrategy { public function pay(float $amount): bool { /* ... */ } }
class PaymentContext {
private array $strategies = [
'alipay' => AlipayStrategy::class,
'wechat' => WechatStrategy::class,
'unionpay'=> UnionPayStrategy::class,
];
public function execute(string $gateway, float $amount): bool {
$class = $this->strategies[$gateway] ?? null;
if (!$class) throw new InvalidArgumentException("Unknown gateway: $gateway");
$strategy = new $class(); // 變數實例化
return $strategy->pay($amount);
}
}
<?php
class HookManager {
private array $hooks = [];
public function addHook(string $event, callable $callback): void {
$this->hooks[$event][] = $callback;
}
public function fire(string $event, mixed &$data = null): void {
foreach ($this->hooks[$event] ?? [] as $callback) {
$callback($data); // 直接呼叫 callable
}
}
}
// 註冊
$hooks = new HookManager();
$hooks->addHook('user.register', function(&$user) { $user['welcome_email'] = true; });
$hooks->addHook('user.register', 'App\Services\LogService::logRegistration'); // 靜態方法字串
// 觸發
$user = ['id' => 1, 'name' => 'John'];
$hooks->fire('user.register', $user);
$_GET['func']()、eval()、create_function() (已移除) 皆為禁忌。is_callable()、method_exists()、class_exists()、interface_exists()。\App\Plugin\\{$name},防止載入任意類別。php.ini disable_functions = exec,shell_exec,system,passthru,proc_open,popen,eval,assert,create_function。Closure::fromCallable() 與 Closure::call() (PHP 7.1+)$method = [new UserService(), 'getProfile'];
$closure = Closure::fromCallable($method); // 建立閉包,綁定 this
$closure = $closure->bindTo(null, 'UserService'); // 可改變綁定範圍
$result = $closure($userId); // 呼叫
ReflectionMethod 或改用直接呼叫。call_user_func_array 因陣列解包額外開銷,參數固定時優先用 $fn(...$args) 或 $obj->$method(...$args)。| 場景 | 推薦寫法 | 安全等級 |
|---|---|---|
| 路由分發 | 白名單陣列 + 變數實例化/方法 | ★★★★★ |
| 策略/演算法切換 | 介面 + 類別名映射 + new $class() | ★★★★★ |
| 事件/鉤子 | callable 陣列 + 直接呼叫 | ★★★★★ |
| 用戶自定義邏輯 | 沙箱 (v8js, php-sandbox) 或 DSL 解析器 | ★★★★☆ |
| 直接執行 $_GET | 嚴禁 | ☠️ RCE |
完整範例專案 (含單元測試、靜態分析規則) 見 GitHub。