Yii2.0源碼分析之——控制器文件分析(Controller.php)創建動作、執行動作


先說一下Yii中的控制器是做什么用的,以及在什么地方使用.

在Yii中,當請求一個Url的時候,首先在application中獲取request信息,然后由request通過urlManager解析出route,再在Module中根據route來創建controller並處理request。
如:http://www.yiifans.com/index.php?r=site/login。會使用SiteController里面的actionLogin動作來處理這個請求。

Yii中總共有三種控制器類

  • base\Controller.php        這個是下面兩個的基類
  • console\Controller.php   這個是控制台控制器
  • web\Controller.php        這個是web控制器



先看看基類base\Controller.php,在基類中大致可分為三個部分

  • 和action相關的功能
  • 和render相關的功能
  • 其它功能


1、和action相關的函數

我們按照這些函數的調用順序來一一說明

執行路由:public function run($route, $params = [])

/*
* route值即可以為當前controller中的action id,
* 
* 也可為module id/controller id/action id/這種格式
* 如果以“/”開頭,將於application來處理,否則,用控制器所屬模塊來處理
*/
public function run($route, $params = [])
{
        //先判斷route中有沒有“/”
        $pos = strpos($route, '/');
        if ($pos === false) {
                //如果沒有“/”,則為action id,直接調用runAction來執行這個action。如:index
            return $this->runAction($route, $params);
        } elseif ($pos > 0) {
                //如果“/”在中間,由當前的模塊來處理這個route。如:test/index
            return $this->module->runAction($route, $params);
        } else {
                //如果以“/”開頭,則用當前的應用程序來處理這個route。如:/test/index;
            return Yii::$app->runAction(ltrim($route, '/'), $params);
        }
}

執行動作:public function runAction($id, $params = [])

/*
* $id 為action的id,如定義的actionIndex,那么id就為Index。
* 
*/
public function runAction($id, $params = [])
{
        //創建action
        $action = $this->createAction($id);
        if ($action === null) {
            throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
        }

        Yii::trace("Route to run: " . $action->getUniqueId(), __METHOD__);

        if (Yii::$app->requestedAction === null) {
            Yii::$app->requestedAction = $action;
        }

        $oldAction = $this->action;
        $this->action = $action;

        //用來保存當前控制器的所有父模塊,順序為由子模塊到父模塊
        $modules = [];
        $runAction = true;

        /*
         * 獲取當前控制器的所以的模塊,並執行每個模塊的beforeAction來檢查當前的action是否可以執行,
         * 注意:getModules返回的數組順序為:從父模塊到子模塊,
         * 所以在執行beforeAction的時候,先檢查最外層的父模塊,然后檢查子模塊。
         * 
         * 然而在執行afterAction的時候,順序就反過來了,先執行子模塊,最后執行父模塊。
         * 
         */
        foreach ($this->getModules() as $module) {
            if ($module->beforeAction($action)) {
                array_unshift($modules, $module);
            } else {
                $runAction = false;
                break;
            }
        }

        $result = null;

        //如果所以的父模塊都滿足執行的條件
        if ($runAction) {
                /*
                 * 再判斷當前控制器中是beforeAction,
                 * 最后由生成的action對象來執行runWithParams方法
                 * 
                 * 執行完后,再執行afterAction方法
                 */
            if ($this->beforeAction($action)) {
                $result = $action->runWithParams($params);
                $result = $this->afterAction($action, $result);
            }
        }

        //執行所有父模塊的afterAction
        foreach ($modules as $module) {
            /** @var Module $module */
            $result = $module->afterAction($action, $result);
        }

        $this->action = $oldAction;

        return $result;
}

創建動作 public function createAction($id)

//由action id來創建action對象
public function createAction($id)
{
        //使用默認的action id ,默認值為:index
        if ($id === '') {
            $id = $this->defaultAction;
        }

        $actionMap = $this->actions();
        if (isset($actionMap[$id])) {
                //如果在actions方法中指定了獨立的動作,則直接使用此動作。
            return Yii::createObject($actionMap[$id], [$id, $this]);
        } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) {
                /*
                 * action id由:a到z、0到9、\、-、_ 這五種字符組成,
                 * 並且不能包含“--”
                 * 並且不能以“-”為開頭或結尾
                 * 
                 * 先以“-”把id分隔為數組,再以“ ”連接到字符串,把每個單詞首字母大寫,最后把“ ”去掉,並和"action"連接
                 * 如;
                 * 1、new-post-v-4
                 * 2、['new','post','v','4']
                 * 3、new post v 4
                 * 4、New Post V 4
                 * 5、NewPostV4
                 * 6、actionNewPostV4
                 */
            $methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
            if (method_exists($this, $methodName)) {
                    /*
                     * 如果當前控制器中存在這個actionXXX方法,
                     * 再通過反射生成方法,再次檢查一遍,最后生成InlineAction
                     */
                $method = new \ReflectionMethod($this, $methodName);
                if ($method->getName() === $methodName) {
                    return new InlineAction($id, $this, $methodName);
                }
            }
        }

        return null;
}

所以,如果一個動作在定義的時候是用駱駝格式名稱的,如actionNewArticle,那么寫url的時候r=site/new-article。詳情見Yii2.0中文開發向導——控制器(Controller)中的路由部分。

定義獨立動作的數組:public function actions()

/*
* 獨立action定義
* 這個用來指定獨立的action,返回格式為name-value的數組,name為action的id,value為action類的實現,如:
* return [
*     'action1' => 'app\components\Action1',
*     'action2' => [
*         'class' => 'app\components\Action2',
*         'property1' => 'value1',
*         'property2' => 'value2',
*     ],
* ];
* 這個主要是用於在子類中重寫
*/
public function actions()
{
        return [];
}

由createAction可知,當controller在創建action的時候,會根據動作ID先在這個數組里面查找,如果找到則返回這個動作。所以這里定義的動作的優先級要大於在控制器里面定義的actionXXX函數。

綁定動作的參數:public function bindActionParams($action, $params)

/*
* 綁定action的參數。
* 比如定義了動作 actionCrate($id,$name=null)
* 那個這個函數的作用就是從params(一般為$_GET)中提取$id,$name,
* 
* 具體的實現在web\Controller.php和console\Controller.php中
*/
public function bindActionParams($action, $params)
{
        return [];
}

beforeAction、afterAction,事件觸發

//在具體的動作執行之前會先執行beforeAction,如果返回false,則動作將不會被執行,
//后面的afterAction也不會執行(但父模塊跌afterAction會執行)
public function beforeAction($action)
{
        $event = new ActionEvent($action);
        $this->trigger(self::EVENT_BEFORE_ACTION, $event);
        return $event->isValid;
}

//當前動作執行之后,執行afterAction
public function afterAction($action, $result)
{
        $event = new ActionEvent($action);
        $event->result = $result;
        $this->trigger(self::EVENT_AFTER_ACTION, $event);
        return $event->result;
}

在這個都會觸發事件,beforeAction觸發EVENT_BEFORE_ACTION事件,afterAction觸發EVENT_AFTER_ACTION

2、和render相關的功能

關於視圖的可以參考Yii2.0中文開發向導——視圖(View)
另外對於視圖文件是怎么查找的請參考:Yii2.0源碼分析之——視圖文件分析(View.php)

獲取、設置view組件:public function getView()、public function setView($view)

//獲取view組件,
public function getView()
{
        if ($this->_view === null) {
            $this->_view = Yii::$app->getView();
        }

        return $this->_view;
}
//設置view組件
public function setView($view)
{
        $this->_view = $view;
}

渲染視圖文件和布局文件(如果有布局的話):public function render($view, $params = [])

//渲染視圖文件和布局文件(如果有布局的話)
public function render($view, $params = [])
{
        //由view對象渲染視圖文件
        $output = $this->getView()->render($view, $params, $this);
        //查找布局文件
        $layoutFile = $this->findLayoutFile($this->getView());
        if ($layoutFile !== false) {
                //由view對象渲染布局文件,
                //並把上面的視圖結果作為content變量傳遞到布局中,所以布局中才會有$content變量來表示
            return $this->getView()->renderFile($layoutFile, ['content' => $output], $this);
        } else {
            return $output;
        }
}

渲染視圖文件,不會應用布局:public function renderPartial($view, $params = [])

//這個只渲染視圖文件,不會應用布局
public function renderPartial($view, $params = [])
{
        return $this->getView()->render($view, $params, $this);
}

渲染文件:public function renderFile($file, $params = [])

//這個就是用來渲染一個文件,$file為文件實路徑或別名路徑
public function renderFile($file, $params = [])
{
        return $this->getView()->renderFile($file, $params, $this);
}

獲取這個控制器對應的view的文件路徑:public function getViewPath()

//獲取這個控制器對應的view的文件路徑,如@app/views/site/xxxx.php
public function getViewPath()
{
        return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
}

查找布局文件:protected function findLayoutFile($view)

//查找布局文件
protected function findLayoutFile($view)
{
        $module = $this->module;
        //如果當前控制器設置了布局文件,則直接使用所設置的布局文件
        if (is_string($this->layout)) {
            $layout = $this->layout;
        } elseif ($this->layout === null) {
                //如果沒有設置布局文件,則查找所有的父模塊的布局文件。
            while ($module !== null && $module->layout === null) {
                $module = $module->module;
            }
            if ($module !== null && is_string($module->layout)) {
                $layout = $module->layout;
            }
        }

        //如果沒有設置布局文件,返回false
        if (!isset($layout)) {
            return false;
        }

        /*
         * 布局文件有三種路徑寫法
         * 1、以“@”開頭,這種會在別名路徑中查找布局文件
         * 2、以“/”開頭,這個會從應用程序的布局文件目錄下面查找布局文件
         * 3、其它情況,   這個會從當前模塊的布局文件目錄下查查找布局文件
         */
        if (strncmp($layout, '@', 1) === 0) {
            $file = Yii::getAlias($layout);
        } elseif (strncmp($layout, '/', 1) === 0) {
            $file = Yii::$app->getLayoutPath() . DIRECTORY_SEPARATOR . substr($layout, 1);
        } else {
            $file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $layout;
        }

        //如果布局文件有文件擴展名,返回
        if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
            return $file;
        }
        //加上默認的文件擴展名。
        $path = $file . '.' . $view->defaultExtension;
        //如果文件不存在,並且,默認的文件擴展名也不是php,則給加上php作為擴展名。
        if ($view->defaultExtension !== 'php' && !is_file($path)) {
            $path = $file . '.php';
        }

        return $path;
}

3、其它功能

獲取當前控制器所有的父模塊:public function getModules()

//獲取當前控制器所有的父模塊
public function getModules()
{
        $modules = [$this->module];
        $module = $this->module;
        while ($module->module !== null) {
                //由這里可知,返回的數組順序為從父模塊到子模塊
            array_unshift($modules, $module->module);
            $module = $module->module;
        }
        return $modules;
}

獲取控制器id:public function getUniqueId()

//返回控制器id
public function getUniqueId()
{
        //如果當前所屬模塊為application,則就為該id,否則要前面要加上模塊id
        return $this->module instanceof Application ? $this->id : $this->module->getUniqueId() . '/' . $this->id;
}

獲取路由信息:public function getRoute()

//獲取路由信息
public function getRoute()
{
        return $this->action !== null ? $this->action->getUniqueId() : $this->getUniqueId();
}

另外還有幾個變量和2個事件

//在執行beforeAction方法時觸發的事件,
//如果對事件的isValid屬性設置為false,將取消action的執行
const EVENT_BEFORE_ACTION = 'beforeAction';
//在執行afterAction方法是觸發的事件
const EVENT_AFTER_ACTION = 'afterAction';
//控制器id
public $id;
//所屬模塊
public $module;
//控制器中默認動作
public $defaultAction = 'index';
//布局文件,如果設置為false,則不使用布局文件
public $layout;
//當前下面執行的action,可在事件中根據這個action來執行不同的操作
public $action;
//視圖對象
private $_view;

原文鏈接:http://www.yiifans.com/forum.php?mod=viewthread&tid=45

Yii2.0源碼分析——目錄

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM