上一篇簡單分析了一下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上最終看到的內容了。由於本次渲染比較簡單,所以程序經過的流程也較少,但是從源碼中可以看到,裡邊進行了許多的處理,比如主題什麼的。本次就先分析到這。晚安!