做為源碼分析的首秀,我就挑了yii(讀作歪依依而不是歪愛愛);它的贊美之詞我就不多說了,直接入正題。先准備材料,建議直從官網下載yii的源碼包(1.1.15)最新版本。
在demos裡邊有一個最簡單的應用—helloworld.就是用yii框架輸出一句話:”hello world”;
我就從它下手,分析框架執行一個最小流程要經過哪些組件,淺析它的運行過程。
首先從單一入口文件開始閱讀。(源碼一般都是從調用處開始分析)
Index.php->
// include Yii bootstrap file
//引入啟動文件
require_once(dirname(__FILE__).'/../../framework/yii.php');
yii.php ->
//YiiBase is a helper class serving common framework functionalities.
//YiiBase是一個助手類,它服務於整個框架。 這裡定義了許多重要的常量
require(dirname(__FILE__).'/YiiBase.php');
//注冊自動加載類
spl_autoload_register(array('YiiBase','autoload'));
//導入接口類
require(YII_PATH.'/base/interfaces.php');
//啟動應用
Yii::createWebApplication()->run();
代碼到這裡似乎就終結了,頁面的內容也程現出來,可是框架到底做了些什麼,我們卻一無所知。所以我們需要把這一步進行分解,把裡邊的細節暴露出來。
Yii::createWebApplication()->run() 一共可以分成三部分
Yii 這個東西是什麼?
從yii.php 可以找到這樣一行代碼class Yii extends YiiBase
說明它就是YiiBase的繼承類,而且作者的擴展是留空的,所以Yii就是YiiBase的一個引用而已。所以createWebApplication這個靜態方法也得去YiiBase中查找了。
在YiiBase.php中,很容易就發現了這個方法:
public static function createWebApplication($config=null)
{
return self::createApplication('CWebApplication',$config);
}
它又把任務傳遞給了createApplication:
public static function createApplication($class,$config=null)
{
return new $class($config);
}
結合起來看,createWebApplication () 就是return new CWebApplication($config);
這個CWebApplication類又在哪呢?它又是怎麼引入的呢?
帶著一系列的問題,我又回到了YiiBase.php
那裡邊定義了一個很長的數組,你可以找到:
'CWebApplication' => '/web/CWebApplication.php',這就是自動加載類中的一員
關於它是如何實現自動加載的,可以查看spl_autoload_register的相關文檔,此處就節外生枝了.
我們繼續往CWebApplication這個裡邊深挖。打開/web/CWebApplication.php這個文件。
前面提到return new CWebApplication($config);根據我的經驗,用了new ,通常會有一個構造函數的,但我卻沒有找到它的構造函數,肯定是在它的父類中,於是我往上找,class CWebApplication extends CApplication 果然被我發現了,這就跟挖泥鳅一樣的,得順著線索一點點的找,要有耐心。
CApplication 中果然有構造函數,代碼如下:
public function __construct($config=null) { Yii::setApplication($this); // set basePath at early as possible to avoid trouble if(is_string($config)) $config=require($config); if(isset($config['basePath'])) { $this->setBasePath($config['basePath']); unset($config['basePath']); } else $this->setBasePath('protected'); Yii::setPathOfAlias('application',$this->getBasePath()); Yii::setPathOfAlias('webroot',dirname($_SERVER['SCRIPT_FILENAME'])); if(isset($config['extensionPath'])) { $this->setExtensionPath($config['extensionPath']); unset($config['extensionPath']); } else Yii::setPathOfAlias('ext',$this->getBasePath().DIRECTORY_SEPARATOR.'extensions'); if(isset($config['aliases'])) { $this->setAliases($config['aliases']); unset($config['aliases']); }
//以上都可以看成是初始化,設置類的引用,別名,路徑什麼的。
$this->preinit();//暫時未發現有什麼用,估計是留給後面擴展用的
$this->initSystemHandlers();//設置錯誤處理
$this->registerCoreComponents();//注冊核心組件
$this->configure($config); //通過配置文件擴展類的屬性,為空的時候什麼也不做
$this->attachBehaviors($this->behaviors);
$this->preloadComponents();
$this->init();
}
$this下面的某些方法,在當前類是找不到的,因為它們可能是來自父類,最簡單的方法就是ctrl+f搜索一下,沒有就去類的聲明處查看:
abstract class CApplication extends CModule
顯然要進入CModule,如果此時還找不到想要方法,那麼繼續上一過程:
abstract class CModule extends CComponent
直到class CComponent
說明這就是當前這些家伙的老巢了。從代碼的中注釋中也可以看到:
CComponent is the base class for all components
說明我的想法是正確的,沒錯,它就是基類。
透過代碼,我們可以發現,我們當前的應用,因為很多參數是空,所以很多邏輯都是直接跳過的。
看到這,$this的內容也大致明了啦。我們再回頭看看
return new CWebApplication($config)->run();
通過前面的層層分析,此時的run方法也很好找了。就在CApplication 裡邊:
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(); 因為前面和後面部分都是注冊事件相關的,當前條件下執行不到。
abstract public function processRequest(); 這個方法在當前類中是抽象的,所以肯定在它的子類中實現了。回去找CWebApplication: public function processRequest() { if(is_array($this->catchAllRequest) && isset($this->catchAllRequest[0])) { $route=$this->catchAllRequest[0]; foreach(array_splice($this->catchAllRequest,1) as $name=>$value) $_GET[$name]=$value; } else $route=$this->getUrlManager()->parseUrl($this->getRequest()); $this->runController($route); }
注意重點在$this->runController($route);
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))); }
我們要注意的代碼只有兩行:
$controller->init();
$controller->run($actionID);
這裡的$controller可以能過查看createController得知,就是默認的控制器Sitecontroller.php
而Action則是index,你問我是怎麼看出來的?哈哈,我在猜不出來的地方echo或var_dump一下不就可以了嗎?這麼簡單的邏輯,還輪不到xdebug 這樣的神器出場。
顯然,init什麼也沒有做,看看run做了什麼
Sitecontroller中沒有run方法,又要去它的父類中查找。
class SiteController extends CController
在CController中有這個方法:
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); }
能過查看$this->createAction($actionID),得到return new CInlineAction($this,$actionID);
我們呆會再看這個CInlineAction,先看$this->runActionWithFilters($action,$this->filters());
public function runActionWithFilters($action,$filters) { if(empty($filters)){ $this->runAction($action); } else { $priorAction=$this->_action; $this->_action=$action; CFilterChain::create($this,$action,$filters)->run(); $this->_action=$priorAction; } }
顯然$filters是空的,所以執行第一個表達式$this->runAction($action);
public function runAction($action) { $priorAction=$this->_action; $this->_action=$action; if($this->beforeAction($action)) { if($action->runWithParams($this->getActionParams())===false){ $this->invalidActionParams($action); } else{ $this->afterAction($action); } } $this->_action=$priorAction; }
這段代碼的重點是 $action->runWithParams($this->getActionParams())這一句;
這裡的$action就是$this->createAction($actionID)返回的結果,而它的結果就是
return new CInlineAction($this,$actionID);
CInlineAction.php
是時候查看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(); }
哇哦,好高級,居然還用了反射,不過我喜歡!
不過呢,打印$method發現: