|-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); 直接執行動作。