上一篇簡單分析了一下yii的流程,從創建一個應用,到屏幕上輸出結果。這一次我來一個稍復雜一點的,重點在輸出上,不再是簡單的一行"hello world",而是要經過view(視圖)層的處理。
依然是demos目錄,這次我們選擇hangman,一個簡單的猜字游戲。老規則,還是從入口處開始看。
index.php:
<?php
// change the following paths if necessary
$yii=dirname(__FILE__).'/../../framework/yii.php';
$config=dirname(__FILE__).'/protected/config/main.php';
// remove the following line when in production mode
// defined('YII_DEBUG') or define('YII_DEBUG',true);
require_once($yii);
Yii::createWebApplication($config)->run();
和helloworld應用相比,這次多了main.php,打開main看下源碼:
<?php
return array(
'name'=>'Hangman Game',
'defaultController'=>'game',
'components'=>array(
'urlManager'=>array(
'urlFormat'=>'path',
'rules'=>array(
'game/guess/<g:\w>'=>'game/guess',
),
),
),
);
在我們以後的實際項目中,也是經常要用到配置文件的,所以我覺得有必要了解一下yii的配置文件--main.php
'name'=>'這裡通常是定義網站的標題',也就是我們打開index.php時,在網頁上顯示的標題。
'defaultController'=>'這裡是默認的控制器',也就是我們的index.php後面沒有指定控制器時系統采用的控制器,如果我們這裡沒有指出來,默認就是site
'components'=>'這裡是組件的參數,用多維數組進行配置。' 具體的參數可以查看yii手冊。
Yii::createWebApplication($config)->run(); 上一次我們已經詳細分析過它了,這裡再簡單的走一遍:
CWebApplication.php -> CApplication.php -> __construct($config) :
$this->preinit();
$this->initSystemHandlers();
$this->registerCoreComponents();
$this->configure($config);
$this->attachBehaviors($this->behaviors);
$this->preloadComponents();
$this->init();
上次我們沒有配置過程,所以$this->configure($config)什麼也沒有做,但是這次有配置參數,所以我們進去看看yii做了哪些操作:
CApplication自己沒有實現configure方法,是繼承於CModule.php的:
public function configure($config)
{
if(is_array($config))
{
foreach($config as $key=>$value)
$this->$key=$value;
}
}
代碼非常簡單,就是把配置參數的鍵做為類的屬性名,value做為類的屬性值進行了擴展。完成這一過程就運行CApplication 上的run方法了。
public function run()
{
if($this->hasEventHandler('onBeginRequest'))
$this->onBeginRequest(new CEvent($this));
register_shutdown_function(array($this,'end'),0,false);
$this->processRequest();
if($this->hasEventHandler('onEndRequest'))
$this->onEndRequest(new CEvent($this));
}
我們前面說過,這裡只要關注 $this->processRequest(); 就可以了。運行的結果就是執行$this->runController('');
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);
$this->_controller=$oldController;
}
else
throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',
array('{route}'=>$route===''?$this->defaultController:$route)));
}
由於url是index.php,後面沒有任何參數,所以都是走的默認控制器,也就是我們在main.php中設定的game. 所以$controller 就等於 controllers/gameController.php, 通過上次的源碼分析我們可以知道,在gameController.php中沒有init方法時,都是走的父類中定義的默認方法(實際上是一個空方法),
$controller->run($actionID); == gameController->run(''); gameController上沒有實現run方法,於是又是去父類中找run
從class GameController extends CController 可以看出,父類是CController , 找到相應的run方法:
public function run($actionID)
{
if(($action=$this->createAction($actionID))!==null)
{
if(($parent=$this->getModule())===null)
$parent=Yii::app();
if($parent->beforeControllerAction($this,$action))
{
$this->runActionWithFilters($action,$this->filters());
$parent->afterControllerAction($this,$action);
}
}
else
$this->missingAction($actionID);
}
前面已經分析過了,沒有指定時,都是默認參數。那麼此時的$actionID為空,actionID就是gameController中定義的默認動作:public $defaultAction='play';
runActionWithFilters ---> runAction --> $action->runWithParams
這裡的$action 需要從CAction -> CInlineAction中去找
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();
}
走了這麼多過程,和hello world的流程是差不多的。據上次的分析可以知道,這裡執行了
$controller->$methodName(); 也就是GameController->actionPlay()
到此,我們本節的重點才真正開始:
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);
}
}
顯然走的是else的邏輯,重點請看 $this->render('play',$params); 這個render方法這麼面熟,很多框架中都有類似的方法,比如discuz,smarty,CI 等等. 縱觀yii框架,rnder 在它整個MVC模式中,是V得以實現的重要骨干。所以有必要把它翻個底朝天。
在CController.php中有這個方法:
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;
}
}
當我們echo $output=$this->renderPartial($view,$data,true);的時候,就發現,此時的$output已經就拿到我們最終的結果了。它對應的文件是views/game/play.php
也就是我們在index.php上最終看到的內容了。由於本次渲染比較簡單,所以程序經過的流程也較少,但是從源碼中可以看到,裡邊進行了許多的處理,比如主題什麼的。本次就先分析到這。晚安!