轉載自:ghypnus 的博客
前端 AJAX → Controller.php (反射調度) → 具體業務類 (LoginController 等)
action=LoginController/login
$('#submit').on('click', function(e) {
e.stopPropagation();
$.ajax({
url: '../../controllers/Controller.php',
type: 'POST',
dataType: 'text',
data: {
action: 'LoginController/login',
username: username,
password: password
},
timeout: 10000,
error: function() { alert('服務器超時'); },
success: function(data) { alert(data); }
});
});
<?php
if (!empty($_REQUEST['action'])) {
try {
$action = explode('/', $_REQUEST['action']);
$className = $action[0];
$methodName = $action[1];
require $className . '.php';
$refClass = new ReflectionClass($className);
if (class_exists($className) && $refClass->hasMethod($methodName)) {
$method = $refClass->getMethod($methodName);
$instance = $refClass->newInstance();
$method->invokeArgs($instance, [$_REQUEST]);
$result = $instance->getResult();
echo $result;
}
} catch (Exception $exc) {
echo $exc->getTraceAsString();
}
}
<?php
class LoginController {
private $result;
function __construct() {
// 初始化數據庫連接等
}
function login($args) {
// 具體登錄邏輯
// $this->result = ...;
}
function getResult() {
return $this->result;
}
}
| 問題 | 風險 | 改進方案 |
|---|---|---|
| 直接 require 用戶輸入類名 | 任意文件包含漏洞 (LFI/RFI) | 白名單驗證、basename 過濾、固定目錄掃描註冊 |
| newInstance() 無參構造 | 無法依賴注入、難以測試 | 使用容器管理實例、或 newInstanceArgs 注入依賴 |
| invokeArgs 直接傳 $_REQUEST | 參數污染、類型不安全、暴露內部結構 | DTO/Request 對象封裝、參數驗證、類型轉換 |
| 異常直接輸出堆疊信息 | 信息洩露、用戶體驗差 | 統一異常處理、友好錯誤碼、日誌記錄 |
| getResult() 耦合返回格式 | 業務類強綁定輸出格式 | 調度器統一包裝響應、業務類返回純數據/異常 |
<?php
// config/routes.php 返回允許的路由映射
$allowedRoutes = [
'login' => ['class' => 'LoginController', 'method' => 'login'],
'register' => ['class' => 'RegisterController', 'method' => 'register'],
// ...
];
$action = $_REQUEST['action'] ?? '';
if (!isset($allowedRoutes[$action])) {
http_response_code(404);
exit(json_encode(['code' => 404, 'msg' => '接口不存在']));
}
$route = $allowedRoutes[$action];
$file = __DIR__ . '/controllers/' . $route['class'] . '.php';
if (!is_file($file)) {
error_log("Controller file missing: {$route['class']}");
exit(json_encode(['code' => 500, 'msg' => '服務器配置錯誤']));
}
require_once $file;
try {
$ref = new ReflectionClass($route['class']);
$method = $ref->getMethod($route['method']);
$instance = $ref->newInstanceWithoutConstructor();
// 參數綁定與驗證
$params = [];
foreach ($method->getParameters() as $param) {
$name = $param->getName();
$params[$name] = $_REQUEST[$name] ?? ($param->isDefaultValueAvailable() ? $param->getDefaultValue() : null);
}
$result = $method->invokeArgs($instance, [$params]);
echo json_encode(['code' => 0, 'data' => $result]);
} catch (ValidationException $e) {
echo json_encode(['code' => 400, 'msg' => $e->getMessage()]);
} catch (Exception $e) {
error_log($e->getMessage());
echo json_encode(['code' => 500, 'msg' => '服務器內部錯誤']);
}
此模式展示了 PHP 反射機制的強大靈活性,但「約定優於配置」若無約束極易失控。生產環境請務必加上白名單、參數驗證、異常處理三重保護。