ThinkPHP 3.x 的控制器調度核心利用 PHP 內建的 ReflectionClass 與 ReflectionMethod 實現:動態方法呼叫、前置/後置操作(_before/_after)、自動參數綁定(從 $_REQUEST 取值並支援預設值)。本文逐行解析核心邏輯,並展示如何在自有框架中複用此機制。
| 類別 | 常用方法 | 用途 |
|---|---|---|
ReflectionClass | hasMethod(), getMethod(), newInstance() | 檢查/取得方法、實例化類別 |
ReflectionMethod | isPublic(), getNumberOfParameters(), getParameters(), invoke(), invokeArgs() | 權限檢查、參數資訊、呼叫方法 |
<?php
// ---------- 範例控制器 ----------
class IndexAction {
public function index() {
echo 'index' . PHP_EOL;
}
public function test($year = 2012, $month = 2, $day = 21) {
echo $year . '--------' . $month . '-----------' . $day . PHP_EOL;
}
public function _before_index() {
echo __FUNCTION__ . PHP_EOL;
}
public function _after_index() {
echo __FUNCTION__ . PHP_EOL;
}
}
// ---------- 調度器核心邏輯 ----------
/**
* 執行指定控制器的 action,包含前置/後置操作與參數自動綁定
*/
function dispatch($controllerClass, $actionName, $requestParams = []) {
$method = new ReflectionMethod($controllerClass, $actionName);
// 1. 權限檢查
if (!$method->isPublic()) {
throw new Exception("Method {$actionName} is not public");
}
$class = new ReflectionClass($controllerClass);
$instance = $class->newInstance(); // 建立實例(可改為單例或 DI 容器取得)
// 2. 執行前置操作 _before_{action}
$beforeMethod = '_before_' . $actionName;
if ($class->hasMethod($beforeMethod)) {
$bm = $class->getMethod($beforeMethod);
if ($bm->isPublic()) {
$bm->invoke($instance);
}
}
// 3. 參數自動綁定並執行主要 action
$params = $method->getParameters();
$args = [];
foreach ($params as $param) {
$name = $param->getName();
if (isset($requestParams[$name])) {
$args[] = $requestParams[$name]; // 從請求取值
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue(); // 使用預設值
} else {
throw new Exception("Missing required parameter: {$name}");
}
}
// 參數數量必須符合
if (count($args) !== $method->getNumberOfParameters()) {
throw new Exception('Parameter count mismatch');
}
$method->invokeArgs($instance, $args);
// 4. 執行後置操作 _after_{action}
$afterMethod = '_after_' . $actionName;
if ($class->hasMethod($afterMethod)) {
$am = $class->getMethod($afterMethod);
if ($am->isPublic()) {
$am->invoke($instance);
}
}
}
// ---------- 使用示範 ----------
echo "=== 執行 index (無參數) ===" . PHP_EOL;
dispatch('IndexAction', 'index');
echo "\n=== 執行 test (帶參數,從 $_GET 模擬) ===" . PHP_EOL;
$_GET = ['year'=>'2023', 'month'=>'12']; // 缺 day,將用預設值 21
dispatch('IndexAction', 'test', $_GET);
=== 執行 index (無參數) ===
_before_index
index
_after_index
=== 執行 test (帶參數,從 $_GET 模擬) ===
2023--------12-----------21
此設計讓控制器方法簽名即為 API 契約,無需手動 $_GET['id'],極大簡化開發。
_before_{actionName}:執行前攔截,常用於權限驗證、資料預處理。_after_{actionName}:執行後收尾,常用於日誌記錄、回應格式化。_before()、_after(),調度器優先檢查具體 action 再回退基底。範例使用 $class->newInstance() 建立無參數建構子的實例。若控制器需注入依賴(如 Service、Model),應改用 $class->newInstanceArgs($dependencies) 或整合 PSR-11 容器。
D()/M() 靜態呼叫。int $id 自動 (int)$_GET['id'])。ThinkPHP 3.x 的反射調度機制雖屬「上一代」技術,但其「約定優於配置、參數自動綁定、前後置攔截」的設計哲學,至今仍是現代 PHP 框架(Laravel、Symfony、ThinkPHP 8)的核心基因。理解其運作原理,有助於在維護舊專案或自研框架時做出更好的架構決策。