一 目錄文件
|-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.
/**
2. * Initializes the class autoloader and error handlers.
3. */
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();
發生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); 直接執行動作。
(摘自http://www.cnblogs.com/codelifezj/archive/2011/10/20/2219252.html)