2017_01_03_01_控制器

控制器
控制器是 MVC 模式中的一部分, 是繼承yii\base\Controller類的對象,負責處理請求和生成響應。 具體來說,控制器從應用主體接管控制後會分析請求數據並傳送到模型, 傳送模型結果到視圖, 最後生成輸出響應信息。
yii2把所有的控制器放在controllers 文件夾下面。
每個類都必須有命名空間,命名空間就是文件存放的目錄路徑。
控制器由 操作 組成,它是執行終端用戶請求的最基礎的單元, 一個控制器可有一個或多個操作。

控制器負責處理請求和產生響應。用戶請求後,控制器將分析請求數據,將它們傳遞到模型,模型中獲得的結果插入的視圖中,並且產生一個響應。
控制器包含動作(操作)。它們是用戶請求執行的基本單位。一個控制器中可以有一個或幾個動作。

命名空間通過自動加載方式聯繫到相應的文件
-----------



在Yii中,當請求一個Url的時候,首先在application中獲取request信息,然後由request通過urlManager解析出route,再在Module中根據route來創建controller並處理request。

Yii中總共有三種控制器類:
    base\Controller.php      這個是下面兩個的基類
    console\Controller.php   這個是控制檯控制器
    web\Controller.php       這個是web控制器

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

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

1、 和action相關的函數
    我們按照這些函數的調用順序來一一說明
    
    a、執行路由: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);
        }
}    

    
    b、執行動作: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;
}


    c、創建動作 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。


    d、定義獨立動作的數組: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函數。


    e、綁定動作的參數: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 [];
}


    f、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相關的功能
   a、獲取、設置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;
}


    b、渲染視圖文件和佈局文件(如果有佈局的話):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;
}

    c、查找佈局文件: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、其它功能

    a、獲取當前控制器所有的父模塊: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;
}


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


    c、獲取路由信息: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;


====================例子===================================

控制器由 操作 組成,它是執行終端用戶請求的最基礎的單元, 一個控制器可有一個或多個操作。
操作必須聲明在控制器中。爲了簡單起見, 你可以直接在 SiteController 控制器裏聲明操作。這個控制器是由文件 controllers/SiteController.php 定義的。
如下示例顯示包含兩個操作view and create 的控制器post:
namespace app\controllers;

use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;

class PostController extends Controller
{
    public function actionView($id)
    {
        $model = Post::findOne($id);
        if ($model === null) {
            throw new NotFoundHttpException;
        }

        return $this->render('view', [
            'model' => $model,
        ]);
    }

    public function actionCreate()
    {
        $model = new Post;

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }
}

在操作 view (定義爲 actionView() 方法)中, 代碼首先根據請求模型ID加載 模型, 如果加載成功,會渲染名稱爲view的視圖並顯示,否則會拋出一個異常。

在操作 create (定義爲 actionCreate() 方法)中, 代碼相似。 先將請求數據填入模型, 然後保存模型,如果兩者都成功,會跳轉到ID爲新創建的模型的view操作, 否則顯示提供用戶輸入的create視圖。

在上述 PostController 代碼中,view 操作被定義爲 actionView 方法。 Yii 使用 action 前綴區分普通方法和操作。 action 前綴後面的名稱被映射爲操作的 ID。

涉及到給操作命名時,你應該理解 Yii 如何處理操作 ID。 操作 ID 總是被以小寫處理,如果一個操作 ID 由多個單詞組成, 單詞之間將由連字符連接(如 create-comment)。操作 ID 映射爲方法名時移除了連字符, 將每個單詞首字母大寫,並加上 action 前綴。 例子:操作 ID create-comment 相當於方法名 actionCreateComment。

上述代碼中的操作方法沒有參數。

在操作方法中,yii\web\Controller::render() 被用來渲染一個名爲 view 的視圖文件。 操作方法會返回渲染結果。 結果會被應用接收並顯示給最終用戶的瀏覽器(作爲整頁 HTML 的一部分)。










    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章