依然是demos目錄,這次我們選擇hangman,一個簡單的猜字游戲。
和helloworld應(yīng)用相比,這次多了main.php,打開main看下源碼:
<?phPReturn array( 'name'=>'Hangman Game', 'defaultController'=>'game', 'components'=>array( 'urlManager'=>array( 'urlFormat'=>'path', 'rules'=>array( 'game/guess/<g:/w>'=>'game/guess', ), ), ),);
在我們以后的實際項目中,也是經(jīng)常要用到配置文件的,所以我覺得有必要了解一下yii的配置文件--main.php
'name'=>'這里通常是定義網(wǎng)站的標(biāo)題',也就是我們打開index.php時,在網(wǎng)頁上顯示的標(biāo)題。
'defaultController'=>'這里是默認(rèn)的控制器',也就是我們的index.php后面沒有指定控制器時系統(tǒng)采用的控制器,如果我們這里沒有指出來,默認(rèn)就是site
'components'=>'這里是組件的參數(shù),用多維數(shù)組進(jìn)行配置。' 具體的參數(shù)可以查看yii手冊。
Yii::createWebapplication($config)->run(); 上一次我們已經(jīng)詳細(xì)分析過它了,這里就不再走一遍了。
上次我們沒有配置過程,所以$this->configure($config)什么也沒有做,但是這次有配置參數(shù),所以我們進(jìn)去看看yii做了哪些操作:
CApplication自己沒有實現(xiàn)configure方法,是繼承于CModule.php的:
    public function configure($config)    {        if(is_array($config))        {            foreach($config as $key=>$value)                $this->$key=$value;        }    }代碼非常簡單,就是把配置參數(shù)的鍵做為類的屬性名,value做為類的屬性值進(jìn)行了擴(kuò)展。
由于url是index.php,后面沒有任何參數(shù),所以都是走的默認(rèn)控制器,也就是我們在main.php中設(shè)定的game. 所以$controller 就等于 controllers/gameController.php, 通過上次的源碼分析我們可以知道,在gameController.php中沒有init方法時,都是走的父類中定義的默認(rèn)方法(實際上是一個空方法),
$controller->run($actionID); == gameController->run(''); 前面已經(jīng)分析過了,沒有指定時,都是默認(rèn)參數(shù)。那么此時的$actionID為空,actionID就是gameController中定義的默認(rèn)動作:public $defaultAction='play';
runActionWithFilters ---> runAction --> $action->runWithParams
走了這么多過程,和hello world的流程是差不多的。據(jù)上次的分析可以知道,這里執(zhí)行了
$controller->$methodName(); 也就是GameController->actionPlay()到此,我們本節(jié)的重點才真正開始:
    public function actionPlay()    {        static $levels=array(            '10'=>'Easy game; you are allowed 10 misses.',            '5'=>'Medium game; you are allowed 5 misses.',            '3'=>'Hard game; you are allowed 3 misses.',        );        // if a difficulty level is correctly chosen        if(isset($_POST['level']) && isset($levels[$_POST['level']]))        {            $this->Word=$this->generateWord();            $this->guessWord=str_repeat('_',strlen($this->word));            $this->level=$_POST['level'];            $this->misses=0;            $this->setPageState('guessed',null);            // show the guess page            $this->render('guess');        }        else        {            $params=array(                'levels'=>$levels,                // if this is a POST request, it means the level is not chosen                'error'=>Yii::app()->request->isPostRequest,            );            // show the difficulty level page            $this->render('play',$params);        }    }重點請看 $this->render('play',$params); 這個render方法這么面熟,很多框架中都有類似的方法,比如discuz,smarty,CI 等等. 縱觀yii框架,render 在它整個MVC模式中,是V得以實現(xiàn)的重要骨干。所以有必要把它翻個底朝天。    public function render($view,$data=null,$return=false)    {        if($this->beforeRender($view))        {            $output=$this->renderPartial($view,$data,true);            if(($layoutFile=$this->getLayoutFile($this->layout))!==false)                $output=$this->renderFile($layoutFile,array('content'=>$output),true);            $this->afterRender($view,$output);            $output=$this->processOutput($output);            if($return)                return $output;            else                echo $output;        }    }先看$output=$this->renderPartial($view,$data,true); 從字面上來看,就是渲染局部視圖。
    public function renderPartial($view,$data=null,$return=false,$processOutput=false)    {        if(($viewFile=$this->getViewFile($view))!==false)        {      //$viewFile=yii/demos/hangman/protected/views/game/play.php            $output=$this->renderFile($viewFile,$data,true);            if($processOutput)                $output=$this->processOutput($output);            if($return)                return $output;            else                echo $output;        }        else            throw new CException(Yii::t('yii','{controller} cannot find the requested view "{view}".',                array('{controller}'=>get_class($this), '{view}'=>$view)));    }這里調(diào)用了renderFile來處理,這個renderfile哪里來的,要自己學(xué)會分析繼承關(guān)系,不明白的看第一篇,我這里直接上源碼了:
    public function renderFile($viewFile,$data=null,$return=false)    {        $widgetCount=count($this->_widgetStack);        if(($renderer=Yii::app()->getViewRenderer())!==null && $renderer->fileExtension==='.'.CFileHelper::getExtension($viewFile)){            $content=$renderer->renderFile($this,$viewFile,$data,$return);        }        else{            $content=$this->renderInternal($viewFile,$data,$return);        }        if(count($this->_widgetStack)===$widgetCount){            return $content;        }        else        {            $widget=end($this->_widgetStack);            throw new CException(Yii::t('yii','{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.',                array('{controller}'=>get_class($this), '{view}'=>$viewFile, '{widget}'=>get_class($widget))));        }    }邏輯上走的是renderInternal方法:
    public function renderInternal($_viewFile_,$_data_=null,$_return_=false)    {        // we use special variable names here to avoid conflict when extracting data        if(is_array($_data_)){            extract($_data_,EXTR_PREFIX_SAME,'data');        }        else{            $data=$_data_;        }        if($_return_)        {            ob_start();            ob_implicit_flush(false);            require($_viewFile_);            return ob_get_clean();        }        else            require($_viewFile_);    }為什么要用ob_start() ?這個方法比較有意思。它非常巧妙的把play.php視圖中的html的內(nèi)容和php輸出的內(nèi)容組裝在了一起。然后整個return 出去,非常值得借鑒的思想。
接著,我們再回到render。走余下的過程。
$output=$this->renderFile($layoutFile,array('content'=>$output),true);這里直接又調(diào)用了一次renderFile方法,這個$layoutFile是什么呢?就是yii/demos/hangman/protected/views/layouts/main.php通過上次的分析,$output就是main.php的內(nèi)容了,不過呢,是結(jié)合了play.php的內(nèi)容。這又是怎么做到的呢?這就是extract($_data_,EXTR_PREFIX_SAME,'data');所發(fā)揮的功能了。還有一個processOutput方法,輸出前進(jìn)行一些緩存什么的,這里先不深究它。
最后就是echo $output;因此屏幕上就有內(nèi)容顯示出來了。
也就是我們在index.php上最終看到的內(nèi)容了。本次渲染比較簡單,但是該走的邏輯都差不多用到了。
總結(jié)一下:視圖層,是通過render方法進(jìn)行渲染輸出的。而render實際上是分成兩部分來做的,一部分是局部視圖,另一部分是通用視圖。
類似于如下結(jié)構(gòu):
<html><!--這里是通用視圖--><head><title>xxx</title></head><body>                    {{這里是局部視圖內(nèi)容}}</body></html>render輸出的是兩者融合的內(nèi)容。這樣做的好處也是顯而易見的,避免創(chuàng)建重復(fù)內(nèi)容。
新聞熱點
疑難解答