Yii PHP 框架分析 (一)
作者:wdy
http://hi.baidu.com/delphiss/blog/item/f7da86d787adb72506088b4b.html
基于yii1.0.8的代碼分析的,。用了一個下午整理的,,流水賬,感興趣的湊合著先看,,國慶期間推出個整理修改版,,然后再完成后兩個部分(MVC和Yii的整體結構分析)。
1. 啟動
網(wǎng)站的唯一入口程序 index.php :
$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();
上面的require_once($yii) 引用出了后面要用到的全局類Yii,,Yii類是YiiBase類的完全繼承:
class Yii extends YiiBase
{
}
系統(tǒng)的全局訪問都是通過Yii類(即YiiBase類)來實現(xiàn)的,,Yii類的成員和方法都是static類型。
2. 類加載
Yii利用PHP5提供的spl庫來完成類的自動加載,。在YiiBase.php 文件結尾處
spl_autoload_register(array('YiiBase','autoload'));
將YiiBase類的靜態(tài)方法autoload 注冊為類加載器,。 PHP autoload 的簡單原理就是執(zhí)行 new 創(chuàng)建對象或通過類名訪問靜態(tài)成員時,系統(tǒng)將類名傳遞給被注冊的類加載器函數(shù),,類加載器函數(shù)根據(jù)類名自行找到對應的類文件并include ,。
下面是YiiBase類的autoload方法:
public static function autoload($className)
{
// use include so that the error PHP file may appear
if(isset(self::$_coreClasses[$className]))
include(YII_PATH.self::$_coreClasses[$className]);
else if(isset(self::$_classes[$className]))
include(self::$_classes[$className]);
else
include($className.'.php');
}
可以看到YiiBase的靜態(tài)成員$_coreClasses 數(shù)組里預先存放著Yii系統(tǒng)自身用到的類對應的文件路徑:
private static $_coreClasses=array(
'CApplication' => '/base/CApplication.php',
'CBehavior' => '/base/CBehavior.php',
'CComponent' => '/base/CComponent.php',
...
)
非 coreClasse 的類注冊在YiiBase的$_classes 數(shù)組中:
private static $_classes=array();
其他的類需要用Yii::import()講類路徑導入PHP include paths 中,直接
include($className.'.php')
3. CWebApplication的創(chuàng)建
回到前面的程序入口的 Yii::createWebApplication($config)->run();
public static function createWebApplication($config=null)
{
return new CWebApplication($config);
}
現(xiàn)在autoload機制開始工作了,。
當系統(tǒng) 執(zhí)行 new CWebApplication() 的時候,,會自動
include(YII_PATH.'/base/CApplication.php')
將main.php里的配置信息數(shù)組$config傳遞給CWebApplication創(chuàng)建出對象,并執(zhí)行對象的run() 方法啟動框架,。
CWebApplication類的繼承關系
CWebApplication -> CApplication -> CModule -> CComponent
$config先被傳遞給CApplication的構造函數(shù)
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']));
$this->preinit();
$this->initSystemHandlers();
$this->registerCoreComponents();
$this->configure($config);
$this->attachBehaviors($this->behaviors);
$this->preloadComponents();
$this->init();
}
Yii::setApplication($this); 將自身的實例對象賦給Yii的靜態(tài)成員$_app,,以后可以通過 Yii::app() 來取得。
后面一段是設置CApplication 對象的_basePath ,指向 proteced 目錄,。
Yii::setPathOfAlias('application',$this->getBasePath());
Yii::setPathOfAlias('webroot',dirname($_SERVER['SCRIPT_FILENAME']));
設置了兩個系統(tǒng)路徑別名 application 和 webroot,,后面再import的時候可以用別名來代替實際的完整路徑,。別名配置存放在YiiBase的 $_aliases 數(shù)組中,。
$this->preinit();
預初始化。preinit()是在 CModule 類里定義的,,沒有任何動作,。
$this->initSystemHandlers() 方法內容:
/**
* Initializes the class autoloader and error handlers.
*/
protected function initSystemHandlers()
{
if(YII_ENABLE_EXCEPTION_HANDLER)
set_exception_handler(array($this,'handleException'));
if(YII_ENABLE_ERROR_HANDLER)
set_error_handler(array($this,'handleError'),error_reporting());
}
設置系統(tǒng)exception_handler和 error_handler,指向對象自身提供的兩個方法,。
4. 注冊核心組件
$this->registerCoreComponents();
代碼如下:
protected function registerCoreComponents()
{
parent::registerCoreComponents();
$components=array(
'urlManager'=>array(
'class'=>'CUrlManager',
),
'request'=>array(
'class'=>'CHttpRequest',
),
'session'=>array(
'class'=>'CHttpSession',
),
'assetManager'=>array(
'class'=>'CAssetManager',
),
'user'=>array(
'class'=>'CWebUser',
),
'themeManager'=>array(
'class'=>'CThemeManager',
),
'authManager'=>array(
'class'=>'CPhpAuthManager',
),
'clientScript'=>array(
'class'=>'CClientScript',
),
);
$this->setComponents($components);
}
注冊了幾個系統(tǒng)組件(Components),。
Components 是在 CModule 里定義和管理的,主要包括兩個數(shù)組
private $_components=array();
private $_componentConfig=array();
每個 Component 都是 IApplicationComponent接口的實例,,Componemt的實例存放在$_components 數(shù)組里,,相關的配置信息存放在$_componentConfig數(shù)組里。配置信息包括Component 的類名和屬性設置,。
CWebApplication 對象注冊了以下幾個Component:urlManager, request,session,assetManager,user,themeManager,authManager,clientScript,。CWebApplication的parent 注冊了以下幾個Component:coreMessages,db,messages,errorHandler,securityManager,statePersister。
Component 在YiiPHP里是個非常重要的東西,,它的特征是可以通過 CModule 的 __get() 和 __set() 方法來訪問,。 Component 注冊的時候并不會創(chuàng)建對象實例,,而是在程序里被第一次訪問到的時候,由CModule 來負責(實際上就是 Yii::app())創(chuàng)建,。
5. 處理 $config 配置
繼續(xù),, $this->configure($config);
configure() 還是在CModule 里:
public function configure($config)
{
if(is_array($config))
{
foreach($config as $key=>$value)
$this->$key=$value;
}
}
實際上是把$config數(shù)組里的每一項傳給 CModule 的 父類 CComponent __set() 方法。
public function __set($name,$value)
{
$setter='set'.$name;
if(method_exists($this,$setter))
$this->$setter($value);
else if(strncasecmp($name,'on',2)===0
&& method_exists($this,$name))
{
//duplicating getEventHandlers() here for performance
$name=strtolower($name);
if(!isset($this->_e[$name]))
$this->_e[$name]=new CList;
$this->_e[$name]->add($value);
}
else if(method_exists($this,'get'.$name))
throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
array('{class}'=>get_class($this), '{property}'=>$name)));
else
throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
array('{class}'=>get_class($this), '{property}'=>$name)));
}
}
我們來看看:
if(method_exists($this,$setter))
根據(jù)這個條件,,$config 數(shù)組里的basePath, params, modules, import, components 都被傳遞給相應的 setBasePath(), setParams() 等方法里進行處理,。
6、$config 之 import
其中 import 被傳遞給 CModule 的 setImport:
public function setImport($aliases)
{
foreach($aliases as $alias)
Yii::import($alias);
}
Yii::import($alias)里的處理:
public static function import($alias,$forceInclude=false)
{
// 先判斷$alias是否存在于YiiBase::$_imports[] 中,,已存在的直接return,, 避免重復import。
if(isset(self::$_imports[$alias])) // previously imported
return self::$_imports[$alias];
// $alias類已定義,,記入$_imports[],,直接返回
if(class_exists($alias,false))
return self::$_imports[$alias]=$alias;
// 類似 urlManager 這樣的已定義于$_coreClasses[]的類,或不含.的直接類名,,記入$_imports[],,直接返回
if(isset(self::$_coreClasses[$alias]) || ($pos=strrpos($alias,'.'))===false) // a simple class name
{
self::$_imports[$alias]=$alias;
if($forceInclude)
{
if(isset(self::$_coreClasses[$alias])) // a core class
require(YII_PATH.self::$_coreClasses[$alias]);
else
require($alias.'.php');
}
return $alias;
}
// 產(chǎn)生一個變量 $className,為$alias最后一個.后面的部分
// 這樣的:'x.y.ClassNamer'
// $className不等于 '*', 并且ClassNamer類已定義的,, ClassNamer' 記入 $_imports[],,直接返回
if(($className=(string)substr($alias,$pos+1))!=='*' && class_exists($className,false))
return self::$_imports[$alias]=$className;
// 取得 $alias 里真實的路徑部分并且路徑有效
if(($path=self::getPathOfAlias($alias))!==false)
{
// $className!=='*',$className 記入 $_imports[]
if($className!=='*')
{
self::$_imports[$alias]=$className;
if($forceInclude)
require($path.'.php');
else
self::$_classes[$className]=$path.'.php';
return $className;
}
// $alias是'system.web.*'這樣的已*結尾的路徑,,將路徑加到include_path中
else // a directory
{
set_include_path(get_include_path().PATH_SEPARATOR.$path);
return self::$_imports[$alias]=$path;
}
}
else
throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing directory or file.',
array('{alias}'=>$alias)));
}
7. $config 之 components
$config 數(shù)組里的 $components 被傳遞給CModule 的setComponents($components)
public function setComponents($components)
{
foreach($components as $id=>$component)
{
if($component instanceof IApplicationComponent)
$this->setComponent($id,$component);
else if(isset($this->_componentConfig[$id]))
$this->_componentConfig[$id]=CMap::mergeArray($this->_componentConfig[$id],$component);
else
$this->_componentConfig[$id]=$component;
}
}
$componen是IApplicationComponen的實例的時候,,直接賦值:
$this->setComponent($id,$component),
public function setComponent($id,$component)
{
$this->_components[$id]=$component;
if(!$component->getIsInitialized())
$component->init();
}
如果$id已存在于_componentConfig[]中(前面注冊的coreComponent),將$component 屬性加進入,。
其他的component將component屬性存入_componentConfig[]中,。
8. $config 之 params
這個很簡單
public function setParams($value)
{
$params=$this->getParams();
foreach($value as $k=>$v)
$params->add($k,$v);
}
configure 完畢!
9. attachBehaviors
$this->attachBehaviors($this->behaviors);
空的,,沒動作
預創(chuàng)建組件對象
$this->preloadComponents();
protected function preloadComponents()
{
foreach($this->preload as $id)
$this->getComponent($id);
}
getComponent() 判斷_components[] 數(shù)組里是否有 $id的實例,,如果沒有,就根據(jù)_componentConfig[$id]里的配置來創(chuàng)建組件對象,,調用組件的init()方法,,然后存入_components[$id]中。
10. init()
$this->init();
函數(shù)內:$this->getRequest();
創(chuàng)建了Reques 組件并初始化,。
11. run()
public function run()
{
$this->onBeginRequest(new CEvent($this));
$this->processRequest();
$this->onEndRequest(new CEvent($this));
}