一 目錄檔案
|-framework 架構核心庫
|--base 底層類庫檔案夾,包含CApplication(應用類,負責全局的使用者請求處理,它管理的應用元件集,将提供特定功能給整個應用程式),CComponent(元件類,該檔案包含了基于元件和事件驅動程式設計的基礎類,從版本1.1.0開始,一個行為的屬性(或者它的公共成員變量或它通過getter和/或setter方法??定義的屬性)可以通過元件的通路來調用),CBehavior(行為類,主要負責聲明事件和相應事件處理程式的方法、将對象的行為附加到元件等等),CModel(模型類,為所有的資料模型提供的基類),CModule(是子產品和應用程式的基類,主要負責應用元件和子子產品)等等
|--caching 所有緩存方法,其中包含了Memcache緩存,APC緩存,資料緩存,CDummyCache虛拟緩存,CEAcceleratorCache緩存等等各種緩存方法
|--cli YII項目生成腳本
|--collections 用php語言構造傳統OO語言的資料存儲單元。如:隊列,棧,哈希表等等
|--console YII控制台
|--db 資料庫操作類
|--gii YII 代碼生成器(腳手架),能生成包括模型,控制器,視圖等代碼
|--i18n YII 多語言,提供了各種語言的本地化資料,資訊、檔案的翻譯服務、本地化日期和時間格式,數字等
|--logging 日志元件,YII提供了靈活和可擴充的日志記錄功能。消息記錄可分為根據日志級别和資訊類别。應用層次和類别過濾器,可進一步選擇的消息路由到不同的目的地,例如檔案,電子郵件,浏覽器視窗,等等|--messages 提示資訊的多語言
|--test YII提供的測試,包括單元測試和功能測試
|--utils 提供了常用的格式化方法
|--validators 提供了各種驗證方法
|--vendors 這個檔案夾包括第三方由Yii架構使用的資料庫
|--views 提供了YII錯誤、日志、配置檔案的多語言視圖
|--web YII所有開發應用的方法
|---actions 控制器操作類
|---auth 權限認識類,包括身份認證,通路控制過濾,基本角色的通路控制等
|---filters 過濾器,可被配置在控制器動作執行之前或之後執行。例如, 通路控制過濾器将被執行以確定在執行請求的動作之前使用者已認證身份驗證;性能過濾器可用于測量控制器執行所用的時間
|---form 表單生成方法
|---helpers 視圖助手,包含GOOGLE AJAX API,建立HTML,JSON,JAVASCRIPT相關功能
|---js JS庫
|---renderers 視圖渲染元件
|---services 封裝SoapServer并提供了一個基于WSDL的Web服務
|---widgets 部件
|---CArrayDataProvider.php 可以配置的排序和分頁屬性自定義排序和分頁的行為
|---CActiveDataProvider.php ActiveRecord方法類
|---CController.php 控制器方法,主要負責協調模型和視圖之間的互動
|---CPagination.php 分頁類
|---CUploadedFile.php 上傳檔案類
|---CUrlManager.php URL管理
|---CWebModule.php 應用子產品管理,應用程式子產品可被視為一個獨立的子應用
等等方法
|--.htaccess 重定向檔案
|--yii.php 引導檔案
|--YiiBase.php YiiBase類最主要的功能是注冊了自動加載類方法,加載架構要用到所有接口。
|--yiic Yii LINUX 指令行腳本
|--yiic.bat YII WINDOW 指令行腳本
|--yiilite.php 它是一些常用到的 Yii 類檔案的合并檔案。在檔案中,注釋和跟蹤語句都被去除。是以,使用 yiilite.php 将減少被引用的檔案數量并避免執行跟蹤語句
二 源碼分析
1. 啟動
網站的唯一入口程式 index.php :
1. $yii= dirname( __FILE__).'/../framework/yii.php';
2. $config= dirname( __FILE__).'/protected/config/main.php';
3.
4. // remove the following line when in production mode
5. defined('YII_DEBUG') or define('YII_DEBUG', true);
6.
7. require_once( $yii);
8. Yii::createWebApplication( $config)->run();
上面的require_once( $yii) 引用出了後面要用到的全局類Yii,Yii類是YiiBase類的完全繼承:
1. class Yii extends YiiBase
2. {
3. }
系統的全局通路都是通過Yii類(即YiiBase類)來實作的,Yii類的成員和方法都是static類型。
2. 類加載
Yii利用PHP5提供的spl庫來完成類的自動加載。在YiiBase.php 檔案結尾處
1. spl_autoload_register( array('YiiBase','autoload'));
将YiiBase類的靜态方法autoload 注冊為類加載器。 PHP autoload 的簡單原理就是執行 new 建立對象或通過類名通路靜态成員時,系統将類名傳遞給被注冊的類加載器函數,類加載器函數根據類名自行找到對應的類檔案并include 。
下面是YiiBase類的autoload方法:
1. public static function autoload( $className)
2. {
3. // use include so that the error PHP file may appear
4. if( isset(self:: $_coreClasses[ $className]))
5. include(YII_PATH.self:: $_coreClasses[ $className]);
6. else if( isset(self:: $_classes[ $className]))
7. include(self:: $_classes[ $className]);
8. else
9. include( $className.'.php');
10. }
可以看到YiiBase的靜态成員 $_coreClasses 數組裡預先存放着Yii系統自身用到的類對應的檔案路徑:
1. private static $_coreClasses= array(
2. 'CApplication' => '/base/CApplication.php',
3. 'CBehavior' => '/base/CBehavior.php',
4. 'CComponent' => '/base/CComponent.php',
5. ...
6. )
非 coreClasse 的類注冊在YiiBase的 $_classes 數組中:
private static $_classes= array();
其他的類需要用Yii::import()講類路徑導入PHP include paths 中,直接
include( $className.'.php')
3. CWebApplication的建立
回到前面的程式入口的 Yii::createWebApplication( $config)->run();
1. public static function createWebApplication( $config= null)
2. {
3. return new CWebApplication( $config);
4. }
現在autoload機制開始工作了。
當系統 執行 new CWebApplication() 的時候,會自動
include(YII_PATH.'/base/CApplication.php')
将main.php裡的配置資訊數組 $config傳遞給CWebApplication建立出對象,并執行對象的run() 方法啟動架構。
CWebApplication類的繼承關系
CWebApplication -> CApplication -> CModule -> CComponent
$config先被傳遞給CApplication的構造函數
1. public function __construct( $config= null)
2. {
3. Yii::setApplication( $this);
4.
5. // set basePath at early as possible to avoid trouble
6. if( is_string( $config))
7. $config= require( $config);
8. if( isset( $config['basePath']))
9. {
10. $this->setBasePath( $config['basePath']);
11. unset( $config['basePath']);
12. }
13. else
14. $this->setBasePath('protected');
15. Yii::setPathOfAlias('application', $this->getBasePath());
16. Yii::setPathOfAlias('webroot', dirname( $_SERVER['SCRIPT_FILENAME']));
17.
18. $this->preinit();
19.
20. $this->initSystemHandlers();
21. $this->registerCoreComponents();
22.
23. $this->configure( $config);
24. $this->attachBehaviors( $this->behaviors);
25. $this->preloadComponents();
26.
27. $this->init();
28. }
Yii::setApplication( $this); 将自身的執行個體對象賦給Yii的靜态成員 $_app,以後可以通過 Yii::app() 來取得。
後面一段是設定CApplication 對象的_basePath ,指向 proteced 目錄。
1. Yii::setPathOfAlias('application', $this->getBasePath());
2. Yii::setPathOfAlias('webroot', dirname( $_SERVER['SCRIPT_FILENAME']));
設定了兩個系統路徑别名 application 和 webroot,後面再import的時候可以用别名來代替實際的完整路徑。别名配置存放在YiiBase的 $_aliases 數組中。
$this->preinit();
預初始化。preinit()是在 CModule 類裡定義的,沒有任何動作。
$this->initSystemHandlers() 方法内容:
1.
4. protected function initSystemHandlers()
5. {
6. if(YII_ENABLE_EXCEPTION_HANDLER)
7. set_exception_handler( array( $this,'handleException'));
8. if(YII_ENABLE_ERROR_HANDLER)
9. set_error_handler( array( $this,'handleError'), error_reporting());
10. }
設定系統exception_handler和 error_handler,指向對象自身提供的兩個方法。
4. 注冊核心元件
$this->registerCoreComponents();
代碼如下:
1. protected function registerCoreComponents()
2. {
3. parent::registerCoreComponents();
4.
5. $components= array(
6. 'urlManager'=> array(
7. 'class'=>'CUrlManager',
8. ),
9. 'request'=> array(
10. 'class'=>'CHttpRequest',
11. ),
12. 'session'=> array(
13. 'class'=>'CHttpSession',
14. ),
15. 'assetManager'=> array(
16. 'class'=>'CAssetManager',
17. ),
18. 'user'=> array(
19. 'class'=>'CWebUser',
20. ),
21. 'themeManager'=> array(
22. 'class'=>'CThemeManager',
23. ),
24. 'authManager'=> array(
25. 'class'=>'CPhpAuthManager',
26. ),
27. 'clientScript'=> array(
28. 'class'=>'CClientScript',
29. ),
30. );
31.
32. $this->setComponents( $components);
33. }
注冊了幾個系統元件(Components)。
Components 是在 CModule 裡定義和管理的,主要包括兩個數組
1. private $_components= array();
2. 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. public function configure( $config)
2. {
3. if( is_array( $config))
4. {
5. foreach( $config as $key=> $value)
6. $this-> $key= $value;
7. }
8. }
實際上是把 $config數組裡的每一項傳給 CModule 的 父類 CComponent __set() 方法。
1. public function __set( $name, $value)
2. {
3. $setter='set'. $name;
4. if( method_exists( $this, $setter))
5. $this-> $setter( $value);
6. else if( strncasecmp( $name,'on',2)===0
7. && method_exists( $this, $name))
8. {
9. // duplicating getEventHandlers() here for performance
10. $name= strtolower( $name);
11. if(! isset( $this->_e[ $name]))
12. $this->_e[ $name]= new CList;
13. $this->_e[ $name]->add( $value);
14. }
15. else if( method_exists( $this,'get'. $name))
16. throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
17. array('{class}'=> get_class( $this), '{property}'=> $name)));
18. else
19. throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
20. array('{class}'=> get_class( $this), '{property}'=> $name)));
21. }
22. }
我們來看看:
if( method_exists( $this, $setter))
根據這個條件, $config 數組裡的basePath, params, modules, import, components 都被傳遞給相應的 setBasePath(), setParams() 等方法裡進行處理。
6、 $config 之 import
其中 import 被傳遞給 CModule 的 setImport:
1. public function setImport( $aliases)
2. {
3. foreach( $aliases as $alias)
4. Yii::import( $alias);
5. }
Yii::import( $alias)裡的處理:
1. public static function import( $alias, $forceInclude= false)
2. {
3. // 先判斷$alias是否存在于YiiBase::$_imports[] 中,已存在的直接return, 避免重複import。
4. if( isset(self:: $_imports[ $alias])) // previously imported
5. return self:: $_imports[ $alias];
6.
7. // $alias類已定義,記入$_imports[],直接傳回
8. if( class_exists( $alias, false))
9. return self:: $_imports[ $alias]= $alias;
10.
11. // 類似 urlManager 這樣的已定義于$_coreClasses[]的類,或不含.的直接類名,記入$_imports[],直接傳回
12. if( isset(self:: $_coreClasses[ $alias]) || ( $pos= strrpos( $alias,'.'))=== false) // a simple class name
13. {
14. self:: $_imports[ $alias]= $alias;
15. if( $forceInclude)
16. {
17. if( isset(self:: $_coreClasses[ $alias])) // a core class
18. require(YII_PATH.self:: $_coreClasses[ $alias]);
19. else
20. require( $alias.'.php');
21. }
22. return $alias;
23. }
24.
25. // 産生一個變量 $className,為$alias最後一個.後面的部分
26. // 這樣的:'x.y.ClassNamer'
27. // $className不等于 '*', 并且ClassNamer類已定義的, ClassNamer' 記入 $_imports[],直接傳回
28. if(( $className=( string) substr( $alias, $pos+1))!=='*' && class_exists( $className, false))
29. return self:: $_imports[ $alias]= $className;
30.
31. // 取得 $alias 裡真實的路徑部分并且路徑有效
32. if(( $path=self::getPathOfAlias( $alias))!== false)
33. {
34. // $className!=='*',$className 記入 $_imports[]
35. if( $className!=='*')
36. {
37. self:: $_imports[ $alias]= $className;
38. if( $forceInclude)
39. require( $path.'.php');
40. else
41. self:: $_classes[ $className]= $path.'.php';
42. return $className;
43. }
44. // $alias是'system.web.*'這樣的已*結尾的路徑,将路徑加到include_path中
45. else // a directory
46. {
47. set_include_path( get_include_path().PATH_SEPARATOR. $path);
48. return self:: $_imports[ $alias]= $path;
49. }
50. }
51. else
52. throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing directory or file.',
53. array('{alias}'=> $alias)));
54. }
7. $config 之 components
$config 數組裡的 $components 被傳遞給CModule 的setComponents( $components)
1. public function setComponents( $components)
2. {
3. foreach( $components as $id=> $component)
4. {
5. if( $component instanceof IApplicationComponent)
6. $this->setComponent( $id, $component);
7. else if( isset( $this->_componentConfig[ $id]))
8. $this->_componentConfig[ $id]=CMap::mergeArray( $this->_componentConfig[ $id], $component);
9. else
10. $this->_componentConfig[ $id]= $component;
11. }
12. }
$componen是IApplicationComponen的執行個體的時候,直接指派:
$this->setComponent( $id, $component),
1. public function setComponent( $id, $component)
2. {
3. $this->_components[ $id]= $component;
4. if(! $component->getIsInitialized())
5. $component->init();
6. }
如果 $id已存在于_componentConfig[]中(前面注冊的coreComponent),将 $component 屬性加進入。
其他的component将component屬性存入_componentConfig[]中。
8. $config 之 params
這個很簡單
1. public function setParams( $value)
2. {
3. $params= $this->getParams();
4. foreach( $value as $k=> $v)
5. $params->add( $k, $v);
6. }
configure 完畢!
9. attachBehaviors
$this->attachBehaviors( $this->behaviors);
空的,沒動作
預建立元件對象
1. $this->preloadComponents();
2.
3. protected function preloadComponents()
4. {
5. foreach( $this->preload as $id)
6. $this->getComponent( $id);
7. }
getComponent() 判斷_components[] 數組裡是否有 $id的執行個體,如果沒有,就根據_componentConfig[ $id]裡的配置來建立元件對象,調用元件的init()方法,然後存入_components[ $id]中。
10. init()
$this->init();
函數内: $this->getRequest();
建立了Reques 元件并初始化。
11. run()
1. public function run()
2. {
3. $this->onBeginRequest( new CEvent( $this));
4. $this->processRequest();
5. $this->onEndRequest( new CEvent( $this));
6. }
三 大概過程
application構造函數:
1 設定目前運作執行個體
2 擷取配置參數
3 設定basepath
4 設定幾個path;application,webroot ,ext
5 preinit
6 注冊error、exception處理函數 initSystemHandlers
7 加載核心元件 registerCoreComponents 包括webapplication的和application的
8 設定配置檔案 configure( $config)
9 附加行為 $this->attachBehaviors( $this->behaviors);
10處理加載config中的preload, // 通過getComponent分别加載并初始化 $this->preloadComponents();
11 初始化init(); // 加載CHttpRequest元件
run:
1 處理onBeginRequest
2 processRequest();真正處理請求
3 處理onEndRequest
webapplication->processRequest():
1 如果配置檔案設定了catchAllRequest , // 'catchAllRequest'=>array('site/error','p1'=>'1','p2'=>'2'),
則所有請求都跳轉到這個controller/action這個route,并且設定 $_GET參數。
2 分析url得到route,便于後面的控制器/動作建立
3 執行runController
runController:
1 建立controller, createController(),建立失敗,則抛出404錯誤
2 得到controller對象和actionID
3 控制器初始化 $controller->init();
4 最後執行 $controller->run( $actionID); // 真正執行頁面請求
控制器類
CController:預設控制器在CWebApplication::defaultController定義('site'),可以在配置檔案修改
run():
1 // 根據actionID建立action對象,這裡生成的action對象分為定義在controller内關聯作和自定義action,比如CViewAction
$action= $this->createAction( $actionID),如果建立動作失敗,missingAction抛出404錯誤
2 beforeControllerAction(beforeControllerAction定義在CWebApplication,有時也在module裡面)為真,才執行runActionWithFilters;
3 afterControllerAction
runActionWithFilters( $action, $this->filters()):
1 // 如果過濾器為空,直接運作runAction()
2 執行過濾器鍊
runAction():
1 beforeAction()傳回真,才執行
2 執行 $action->runWithParams();注意:這裡存在多态,每個action都可以實作這個方法, 因為CInlineAction自己實作了runWithParams()
3 第2步驟為真,才執行afterAction( $action);
動作類 預設動作在CController:: $defaultAction定義('index'),可以在CController的繼承類重新定義
runWithParams():
1 分為2種情況,1種是内關聯作,1種是通過控制器的actions方法定義的外關聯作。
2 内關聯作 通過action+動作id作為動作處理函數
3 外關聯作 通過調用run()函數來實作
4 如果動作方法參數個數大于0,執行runWithParamsInternal,否則直接執行動作方法。
runWithParamsInternal();
1 根據反射的方法對象得到方法的形參清單,從 控制器對象->getActionParams()得到實參,
如果實參有形參要求的參數,取其值,不然取形參預設值,否則,出錯。
2 調用動作方法 2種形式 1是action+動作id ,2是Caction的派生類(比如cviewaction)的run()
3 執行控制器的CController->render方法; $controller->render( $view)
控制器類
CController:
render();
1 renderPartial();得到視圖, // 先得到contact頁面的view檔案内容,注意是用include的形式,是以其中的$this是指siteControlerd對象,
這裡調用了renderFile();
2 然後 $output= $this->renderFile( $layoutFile, array('content'=> $output), true)
把view中的内容插入到布局頁面layouts的column1.php, 'content'和layout的頁面的 $content變量相關
renderFile();
1 如果程式沒有定義viewrender,則執行controller->renderInternal();否則,執行 $renderer=Yii::app()->getViewRenderer())->renderFile();
view sourceprint?發生404錯誤
errorHandler 在配置檔案main中,'errorAction' => 'site/error',
**********************************************************
runActionWithFilters
過濾:
CFilterChain(繼承CList,提供數字索引存取功能,周遊)::create( $this, $action, $filters)->run();
首先建立過濾鍊,然後執行過濾
CFilterChain::create( $controller, $action, $filters):
1 $chain= new CFilterChain( $controller, $action);建立一個過濾鍊 $chain
2 根據參數filters數組,周遊建立過濾器 $filter(字元串:通過CInlineFilter::create或者 數組:Yii::createComponent),
并且初始化 $filter::init,通過 $chain->add( $filter)添加到過濾鍊中,并且傳回這個過濾鍊 $chain
注意:如果是字元串,控制器類controller必須要有"filter"+過濾器名的方法。
$chain::run();
1 如果數字索引合法,得到 $filter,然後執行 $filter->filter( $this);
1.1 $filter->filter( $this):
1 執行動作的'filter'+過濾器名稱的方法。 // 比如CController::filterAccessControl($filterChain);
1.1.1 CController::filterAccessControl( $filterChain):
1 $filter= new CAccessControlFilter; // 建立過濾器
2 $filter->setRules( $this->accessRules()); // 設定規則
3 $filter->filter( $filterChain) ; // 執行過濾
4 $filter->preFilter( $filterChain)為真,繼續執行 $filterChain->run();
5 $filter->postFilter( $filterChain); // 這個是在動作執行之後過濾
2 否則,說明過濾完畢, $this->controller->runAction( $this->action); 直接執行動作