http://hi.baidu.com/iwangdy/item/cc7c2cef6a5618d5ea34c98e
基於yii1.0.8的代碼分析的。用了一個下午整理的,流水賬,感興趣的湊合着先看,國慶期間推出個整理修改版,然後再完成後兩個部分(MVC和Yii的整體結構分析)。
1. 啓動
網站的唯一入口程序 index.php :
1
2
3
4
5
6
|
$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
{
}
系統的全局訪問都是通過Yii類(即YiiBase類)來實現的,Yii類的成員和方法都是static類型。
2. 類加載
Yii利用PHP5提供的spl庫來完成類的自動加載。在YiiBase.php 文件結尾處
spl_autoload_register(array('YiiBase','autoload'));
將YiiBase類的靜態方法autoload 註冊爲類加載器。 PHP autoload 的簡單原理就是執行 new 創建對象或通過類名訪問靜態成員時,系統將類名傳遞給被註冊的類加載器函數,類加載器函數根據類名自行找到對應的類文件並include 。
下面是YiiBase類的autoload方法:
1
2
3
4
5
6
7
8
9
10
|
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的靜態成員$_coreClasses 數組裏預先存放着Yii系統自身用到的類對應的文件路徑:
private static $_coreClasses=array(
'CApplication' => '/base/CApplication.php',
'CBehavior' => '/base/CBehavior.php',
'CComponent' => '/base/CComponent.php',
...
)
非 coreClasse 的類註冊在YiiBase的$_classes 數組中:
private static $_classes=array();
其他的類需要用Yii::import()講類路徑導入PHP include paths 中,直接
include($className.'.php')
3. CWebApplication的創建
回到前面的程序入口的 Yii::createWebApplication($config)->run();
1
2
3
4
|
public static function createWebApplication( $config =null) { return new CWebApplication( $config ); } |
現在autoload機制開始工作了。
當系統 執行 new CWebApplication() 的時候,會自動
include(YII_PATH.'/base/CApplication.php')
將main.php裏的配置信息數組$config傳遞給CWebApplication創建出對象,並執行對象的run() 方法啓動框架。
CWebApplication類的繼承關係
CWebApplication -> CApplication -> CModule -> CComponent
$config先被傳遞給CApplication的構造函數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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的靜態成員$_app,以後可以通過 Yii::app() 來取得。
後面一段是設置CApplication 對象的_basePath ,指向 proteced 目錄。
Yii::setPathOfAlias('application',$this->getBasePath());
Yii::setPathOfAlias('webroot',dirname($_SERVER['SCRIPT_FILENAME']));
設置了兩個系統路徑別名 application 和 webroot,後面再import的時候可以用別名來代替實際的完整路徑。別名配置存放在YiiBase的 $_aliases 數組中。
$this->preinit();
預初始化。preinit()是在 CModule 類裏定義的,沒有任何動作。
$this->initSystemHandlers() 方法內容:
1
2
3
4
5
6
7
8
9
10
|
/** * 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 ()); } |
設置系統exception_handler和 error_handler,指向對象自身提供的兩個方法。
4. 註冊核心組件
$this->registerCoreComponents();
代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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 ); } |
註冊了幾個系統組件(Components)。
Components 是在 CModule 裏定義和管理的,主要包括兩個數組
private $_components=array();
private $_componentConfig=array();
每個 Component 都是 IApplicationComponent接口的實例,Componemt的實例存放在$_components 數組裏,相關的配置信息存放在$_componentConfig數組裏。配置信息包括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 註冊的時候並不會創建對象實例,而是在程序裏被第一次訪問到的時候,由CModule 來負責(實際上就是 Yii::app())創建。
5. 處理 $config 配置
繼續, $this->configure($config);
configure() 還是在CModule 裏:
1
2
3
4
5
6
7
8
|
public function configure( $config ) { if ( is_array ( $config )) { foreach ( $config as $key => $value ) $this -> $key = $value ; } } |
實際上是把$config數組裏的每一項傳給 CModule 的 父類 CComponent __set() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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))
根據這個條件,$config 數組裏的basePath, params, modules, import, components 都被傳遞給相應的 setBasePath(), setParams() 等方法裏進行處理。
6、$config 之 import
其中 import 被傳遞給 CModule 的 setImport:
1
2
3
4
5
|
public function setImport( $aliases ) { foreach ( $aliases as $alias ) Yii::import( $alias ); } |
Yii::import($alias)裏的處理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
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 ; } // 產生一個變量 $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 數組裏的 $components 被傳遞給CModule 的setComponents($components)
1
2
3
4
5
6
7
8
9
10
11
12
|
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),
1
2
3
4
5
6
|
public function setComponent( $id , $component ) { $this ->_components[ $id ]= $component ; if (! $component ->getIsInitialized()) $component ->init(); } |
如果$id已存在於_componentConfig[]中(前面註冊的coreComponent),將$component 屬性加進入。
其他的component將component屬性存入_componentConfig[]中。
8. $config 之 params
這個很簡單
1
2
3
4
5
6
|
public function setParams( $value ) { $params = $this ->getParams(); foreach ( $value as $k => $v ) $params ->add( $k , $v ); } |
configure 完畢!
9. attachBehaviors
$this->attachBehaviors($this->behaviors);
空的,沒動作
預創建組件對象
$this->preloadComponents();
1
2
3
4
5
|
protected function preloadComponents() { foreach ( $this ->preload as $id ) $this ->getComponent( $id ); } |
getComponent() 判斷_components[] 數組裏是否有 $id的實例,如果沒有,就根據_componentConfig[$id]裏的配置來創建組件對象,調用組件的init()方法,然後存入_components[$id]中。
10. init()
$this->init();
函數內:$this->getRequest();
創建了Reques 組件並初始化。
11. run()
1
2
3
4
5
6
|
public function run() { $this ->onBeginRequest( new CEvent( $this )); $this ->processRequest(); $this ->onEndRequest( new CEvent( $this )); }
|