轉載請註明:TheViper https://www.cnblogs.com/TheViper/
上一篇分析了 CWebApplication 中透過 getUrlManager()->parseUrl() 解析路由得到 $route = controller/action。本篇將深入探討後續的 runController($route) 執行流程。
<?php
class CWebApplication extends CApplication {
// ... 屬性定義 ...
public function processRequest() {
$route = $this->getUrlManager()->parseUrl($this->getRequest());
$this->runController($route);
}
public function runController($route) {
if (($ca = $this->createController($route)) !== null) {
list($controller, $actionID) = $ca;
$oldController = $this->_controller;
$this->_controller = $controller;
$controller->init(); // 鉤子方法,子類別覆寫
$controller->run($actionID); // 進入控制器執行 action
$this->_controller = $oldController;
}
}
public function createController($route, $owner = null) {
// 解析 route 字串,建立控制器實例
// 回傳 [controller實例, actionID]
}
// ... 其他方法 ...
}
?>
createController() 會從路由字串解析出控制器 ID 與動作 ID,載入對應的控制器類別檔案並實例化。控制器初始化時會呼叫 init() 鉤子,開發者可在子類別中覆寫,例如初始化資料庫連線:
<?php
class VideoController extends CController {
public function init() {
$this->db = Yii::app()->db;
}
public function actionBroadcast() {
$b = $this->db->query("", array(1));
$this->render("u_broadcast", array('b' => $b[0]));
}
}
?>
<?php
class CController {
public function run($actionID) {
if (($action = $this->createAction($actionID)) !== null) {
$parent = Yii::app();
if ($parent->beforeControllerAction($this, $action)) {
$this->runActionWithFilters($action, $this->filters());
$parent->afterControllerAction($this, $action);
}
}
}
public function runActionWithFilters($action, $filters) {
if (empty($filters))
$this->runAction($action);
else {
$priorAction = $this->_action;
$this->_action = $action;
CFilterChain::create($this, $action, $filters)->run();
$this->_action = $priorAction;
}
}
public function runAction($action) {
$priorAction = $this->_action;
$this->_action = $action;
if ($this->beforeAction($action)) {
if ($action->runWithParams($this->getActionParams()) === false)
$this->invalidActionParams($action);
else
$this->afterAction($action);
}
$this->_action = $priorAction;
}
// ... render, forward, createAction 等方法 ...
}
?>
Yii 使用 CInlineAction 封裝控制器中的動作方法,透過 PHP 反射 (ReflectionMethod) 動態呼叫:
<?php
class CInlineAction extends CAction {
public function runWithParams($params) {
$methodName = 'action' . $this->getId();
$controller = $this->getController();
$method = new ReflectionMethod($controller, $methodName);
if ($method->getNumberOfParameters() > 0)
return $this->runWithParamsInternal($controller, $method, $params);
else
return $controller->$methodName();
}
}
?>
當控制器定義 filters() 時,Yii 會建立 CFilterChain 依序執行過濾器。過濾器支援針對特定動作啟用(如 'application.filters.LoginFilter+upload_video')。
<?php
class CFilterChain extends CList {
public function run() {
if ($this->offsetExists($this->filterIndex)) {
$filter = $this->itemAt($this->filterIndex++);
$filter->filter($this); // 執行前置/後置過濾
} else {
$this->controller->runAction($this->action); // 所有過濾器完成,執行實際動作
}
}
public static function create($controller, $action, $filters) {
// 解析過濾器設定,建立 CInlineFilter 或 CFilter 實例
}
}
?>
<?php
class LoginFilter extends CFilter {
protected function preFilter($filterChain) {
if (!isset($_SESSION)) session_start();
if (isset($_SESSION['user'])) return true;
else {
setcookie("return", Yii::app()->getRequest()->getUrl(), time()+360, '/');
Yii::app()->getRequest()->redirect('https://localhost/youtube/login', true, 302);
return false;
}
}
protected function postFilter($filterChain) {}
}
?>
Yii 的請求處理流程:UrlManager 解析路由 → 建立控制器 → 初始化 → 執行動作(含過濾器鏈)→ 渲染視圖。過濾器鏈透過遞迴方式依序執行,最後回歸 runAction() 執行實際業務邏輯。forward() 方法可實現內部轉向,本質上是再次呼叫 runController()。
附上裁剪版 Yii 框架:framework.zip