Yaf源碼閱讀之框架的啓動(一)

(一)Yaf是什麼php

Yaf,全稱 Yet Another Framework,是一個C語言編寫的、基於PHP擴展開發的PHP框架,html

相比於通常的PHP框架,它更快,快到被譽爲最快的PHP開發框架。前端

它提供了Bootstrap、路由、分發、視圖、插件功能。web

 

Yaf由惠新宸(傳說中的鳥哥)開發,隆重介紹下,惠新宸,PHP開發組核心成員,數據庫

PECL開發者, Zend公司外聘顧問, 曾供職於雅虎,百度,現爲新浪微博架構師兼首席PHP技術顧問,是PHP5.4, 5.5的主要開發者。做爲PECL開發者貢獻了Yaf (Yet another framework),Yar(Yet another RPC framework)以及Yac(Yet another Cache)、Taint等多個優秀開源做品,同時也是APC,Opcache,Msgpackbootstrap

等項目的維護者...此處略去1000字。總之是一位牛得掉渣的人,Yaf在百度和新浪微博獲得普遍使用。後端

在百度,絕大部分產品都是PHP開發的,其中絕大部分PHP程序都使用了Yaf框架;在新浪微博,Yaf也獲得了普遍使用。這些公司的產品都是高併發、超大流量的,Yaf經受住了考驗。緩存

 

(二)Yaf的優勢安全

  • 用C語言開發的PHP框架,相比原生的PHP, 幾乎不會帶來額外的性能開銷。
  • 全部的框架類,不須要編譯,在PHP啓動的時候加載, 並常駐內存。
  • 更短的內存週轉週期,提升內存利用率,下降內存佔用率。
  • 靈巧的自動加載。支持全局和局部兩種加載規則,方便類庫共享。
  • 高度靈活可擴展的框架,支持自定義視圖引擎,支持插件,支持自定義路由等等。
  • 內建多種路由,能夠兼容目前常見的各類路由協議。
  • 強大而又高度靈活的配置文件支持。並支持緩存配置文件,避免複雜的配置結構帶來的性能損失。
  • 在框架自己,對危險的操做習慣作了禁止。
  • 更快的執行速度, 更少的內存佔用。

以上內容引用鳥哥的官方介紹,固然,Yaf不是一個Full-Stack的web框架,它沒有對數據庫操做session

的封裝,更不用說ORM;它不提供表單生成、驗證、分頁、國際化、JS和CSS壓縮等跟前端耦合

得比較緊密的功能;它不提供過濾器、緩存、組件這些web框架中經常使用的功能。不少人認爲這是

Yaf的不足,但我認爲這又是Yaf的優勢,這表明着一種精神,就是追求簡單,追求高效,追求」簡單可依賴「, 因此Yaf專一於實現最核心的功能,提供最穩定的實現。Yaf不提供上面這些功能是有道理的,不提供ORM,

是由於PHP已經提供了對DB的一個輕度封裝的PDO, 直接使用PDO, 會更加簡單, 更加高效。不提供和

前端有關的功能,是由於在互聯網產品,前端和PHP後端每每分離得很清楚,PHP只專一於組裝數據給前端模板用,至於網頁的佈局、分頁、表單的驗證、JS和CSS的壓縮等,交給更加專業的前端工程師解決更合適。儘管Yaf的功能有限,但Yaf是可擴展的!它提供的插件機制,能夠和其它類庫整合在一塊兒。

總之Yaf很是適合互聯網產品的開發。好比整合Smarty模板引擎,甚至能夠根據產品的業務特色,基於Yaf

再擴展一套適合本身的業務層框架。總之Yaf很是適合互聯網產品的開發。

 

(三)爲何要閱讀Yaf的源代碼

Yaf最大的不足在於缺乏文檔,別擔憂,Yaf的源代碼就是最好的文檔,Yaf的核心代碼才幾千行,

可讀性很強,經過閱讀代碼,你能夠:

  • 學會如何作PHP擴展開發
  • 學會如何實現一個WEB框架
  • 理解Yaf的內部實現,更有助於開發應用

何樂而不爲,一塊兒來讀代碼吧!

 

(四)Yaf的啓動

Yaf的啓動包括配置的初始化和框架類的加載,Yaf是一個PHP擴展,理解Yaf得先理解PHP擴展的原理,

咱們先從PHP程序的生命週期提及。

4.1 PHP程序的生命週期

一個PHP程序,依次通過Module init、Request init、Request shutdown、Module shutdown四個過程,

固然,之間還會執行腳本自身的代碼。在命令行模式下運行一個PHP程序的主要流程如圖4-1所示:



                                        圖4-1 PHP生命週期

實現PHP擴展,便是在以上4個階段定義4個相應地函數,供PHP在執行的時候調用。

(1)Module init階段的函數

C代碼   收藏代碼
  1. PHP_MINIT_FUNCTION(myext)  
  2. {  
  3.     //註冊常量或者類等初始化操做  
  4.     return SUCCESS;  
  5. }  

 這個函數在擴展被載入時調用。

(2)Request test.php

請求test.php文件。當請求到達後,PHP會初始化執行腳本的基本環境,包括保存PHP運行過程當中變量名稱

和變量值內容的符號表,以及當前全部的函數以及類等信息的符號表。

(3)Request init階段的函數

 

C代碼   收藏代碼
  1. PHP_RINIT_FUNCTION(myext)  
  2. {  
  3.     //例如記錄請求開始時間  
  4.     //隨後在請求結束的時候記錄結束時間  
  5.     //這樣就可以記錄處理請求所花費的時間了  
  6.     return SUCCESS;  
  7. }  
 而後PHP會調用全部模塊的RINIT函數。

 

(4)Execute test.php

執行test.php階段,主要是把PHP文件編譯成Opcodes,而後在PHP虛擬機下執行。

 

(5)Request shutdown階段的函數

C代碼   收藏代碼
  1. PHP_RSHUTDOWN_FUNCTION(myext)  
  2. {  
  3.     //例如記錄請求結束時間,並把  
  4.     //相應地信息寫入到日誌文件中  
  5.     return SUCCESS;  
  6. }  

 請求處理完成後進入結束階段,通常腳本執行到末尾或者經過調用exit()或者die()函數,PHP都將進入結束

階段。和開始階段對應,結束階段也分爲兩個環節,一個在請求結束後(RSHUTDOWN),一個在SAPI生命週期結束時(MSHUTDOWN)。

 

(6)Module shutdown階段的函數:

C代碼   收藏代碼
  1. PHP_MSHUTDOWN_FUNCTION(myext)  
  2. {  
  3.     //註銷一些持久化的資源  
  4.     return SUCCESS;  
  5. }  

 

 在請求一個PHP頁面時,PHP基本上是按照這個流程執行的,Yaf擴展也是圍繞這個流程,插入本身的代碼,

進而使Yaf框架影響到PHP的請求中。

 

4.2 Yaf擴展配置的初始化

擴展能夠在php.ini中寫本身的配置信息,或者在編譯PHP時--with-config-file-scan-dir指定目錄中的配置

文件好比yaf.ini中寫配置信息。Yaf擴展提供的配置項如表4-1所示

選項名稱 默認值 可修改範圍 說明
yaf.environ product PHP_INI_ALL 環境名稱, 當用INI做爲Yaf的配置文件時, 這個指明瞭Yaf將要在INI配置中讀取的節的名字
yaf.library NULL PHP_INI_ALL 全局類庫的目錄路徑
yaf.cache_config 0 PHP_INI_SYSTEM 是否緩存配置文件(只針對INI配置文件生效), 打開此選項可在複雜配置的狀況下提升性能
yaf.name_suffix 1 PHP_INI_ALL 在處理Controller, Action, Plugin, Model的時候, 類名中關鍵信息是不是後綴式, 好比UserModel, 而在前綴模式下則是ModelUser
yaf.name_separator "" PHP_INI_ALL 在處理Controller, Action, Plugin, Model的時候, 前綴和名字之間的分隔符, 默認爲空, 也就是UserPlugin, 加入設置爲"_", 則判斷的依據就會變成:"User_Plugin", 這個主要是爲了兼容ST已有的命名規範
yaf.forward_limit 5 PHP_INI_ALL forward最大嵌套深度
yaf.use_namespace 0 PHP_INI_ALL 開啓的狀況下, Yaf將會使用命名空間方式註冊本身的類, 好比Yaf_Application將會變成Yaf\Application
yaf.use_spl_autoload 0 PHP_INI_SYSTEM 開啓的狀況下, Yaf在加載不成功的狀況下, 會繼續讓PHP的自動加載函數加載, 從性能考慮, 除非特殊狀況, 不然保持這個選項關閉

                                                    表4-1 Yaf的配置選項

 

那麼Yaf是如何讀取配置文件,並初始化這些參數呢?

讀取配置文件以前,得定義好參數,即聲明變量來保存參數的值:

C代碼   收藏代碼
  1. PHP_INI_BEGIN()  
  2.     STD_PHP_INI_ENTRY("yaf.library",            "",  PHP_INI_ALL, OnUpdateString, global_library, zend_yaf_globals, yaf_globals)  
  3.     STD_PHP_INI_BOOLEAN("yaf.action_prefer",    "0", PHP_INI_ALL, OnUpdateBool, action_prefer, zend_yaf_globals, yaf_globals)  
  4.     STD_PHP_INI_BOOLEAN("yaf.lowcase_path",     "0", PHP_INI_ALL, OnUpdateBool, lowcase_path, zend_yaf_globals, yaf_globals)  
  5.     STD_PHP_INI_BOOLEAN("yaf.use_spl_autoload""0", PHP_INI_ALL, OnUpdateBool, use_spl_autoload, zend_yaf_globals, yaf_globals)  
  6.     STD_PHP_INI_ENTRY("yaf.forward_limit",      "5", PHP_INI_ALL, OnUpdateLongGEZero, forward_limit, zend_yaf_globals, yaf_globals)  
  7.     STD_PHP_INI_BOOLEAN("yaf.name_suffix",      "1", PHP_INI_ALL, OnUpdateBool, name_suffix, zend_yaf_globals, yaf_globals)  
  8.     PHP_INI_ENTRY("yaf.name_separator",         "",  PHP_INI_ALL, OnUpdateSeparator)  
  9.     STD_PHP_INI_BOOLEAN("yaf.cache_config",     "0", PHP_INI_SYSTEM, OnUpdateBool, cache_config, zend_yaf_globals, yaf_globals)  
  10. /* {{{ This only effects internally */  
  11.     STD_PHP_INI_BOOLEAN("yaf.st_compatible",     "0", PHP_INI_ALL, OnUpdateBool, st_compatible, zend_yaf_globals, yaf_globals)  
  12. /* }}} */  
  13.     STD_PHP_INI_ENTRY("yaf.environ",            "product", PHP_INI_SYSTEM, OnUpdateString, environ, zend_yaf_globals, yaf_globals)  
  14. #ifdef YAF_HAVE_NAMESPACE  
  15.     STD_PHP_INI_BOOLEAN("yaf.use_namespace",    "0", PHP_INI_SYSTEM, OnUpdateBool, use_namespace, zend_yaf_globals, yaf_globals)  
  16. #endif  
  17. PHP_INI_END();  

 定義參數時,使用宏 PHP_INI_BEGIN() 來標識的開始,並用 PHP_INI_END() 表示該配置節已經結束。而後在二者之間咱們用 PHP_INI_ENTRY() 來建立具體的配置項。PHP_INI_ENTRY 這個宏裏面設置的前面的兩個參數,分別表明着INI設置的名稱和它的默認值。第二個參數決定設置是否容許被修改,以及它能被修改的做用域。最後一個參數是一個回調函數,當INI的值被修改時候觸發此回調函數。你將會在某些修改事件的地方詳細的瞭解這個參數。最後定義好的參數結構體以下:

C代碼   收藏代碼
  1. static zend_ini_entry ini_entries[] = {  //   BEGIN 的定義  
  2.    { 0, PHP_INI_ALL, "yaf.library"sizeof("yaf.library"), NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, NULL},  
  3. ...  
  4.    { 0, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, NULL } };    // END的定義  

 接下來在 Module init階段讀取配置文件,並把參數值填充到 init_entries 結構體中,由REGISTER_INI_ENTRIES函數完成:

C代碼   收藏代碼
  1. PHP_MINIT_FUNCTION(yaf)  
  2. {  
  3.     REGISTER_INI_ENTRIES();  
  4.         ......  
  5. }  

 

4.3 application配置的初始化

4.3.1 application的配置選項

與yaf的全局配置不同,application的配置是針對單個應用的,配置項如表4-2所示:

名稱 值類型 默認值 說明
application.directory String 應用的絕對目錄路徑  
application.ext String php PHP腳本的擴展名
application.bootstrap String Bootstrapplication.php Bootstrap路徑(絕對路徑)
application.library String application.directory + "/library" 本地(自身)類庫的絕對目錄地址
application.baseUri String NULL 在路由中, 須要忽略的路徑前綴, 通常不須要設置, Yaf會自動判斷.
application.dispatcher.defaultModule String index 默認的模塊
application.dispatcher.throwException Bool TRUE 在出錯的時候, 是否拋出異常
application.dispatcher.catchException Bool FALSE 是否使用默認的異常捕獲Controller, 若是開啓, 在有未捕獲的異常的時候, 控制權會交給ErrorController的errorAction方法, 能夠經過$request->getException()得到此異常對象
application.dispatcher.defaultController String index 默認的控制器
application.dispatcher.defaultAction String index 默認的動做
application.view.ext String phtml 視圖模板擴展名
application.modules String Index 聲明存在的模塊名, 請注意, 若是你要定義這個值, 必定要定義Index Module
application.system.* String * 經過這個屬性, 能夠修改yaf的runtime configure, 好比application.system.lowcase_path, 可是請注意只有PHP_INI_ALL的配置項才能夠在這裏被修改, 此選項從2.2.0開始引入

                         表4-2 application的配置選項

 

4.3.2 全局變量的定義

application的配置保存在Yaf的全局變量中,全局變量的定義方式爲:

php_yaf.h

C代碼   收藏代碼
  1. ZEND_BEGIN_MODULE_GLOBALS(yaf)  
  2.     char        *ext;  
  3.     char        *base_uri;  
  4.     char        *environ;  
  5.     char        *directory;  
  6.     char        *local_library;  
  7.     ...  
  8. ZEND_END_MODULE_GLOBALS(yaf)  
 展開後變成這樣:

 

C代碼   收藏代碼
  1. typedef struct _zend_yaf_globals {  
  2.     unsigned long counter;  
  3. } zend_yaf_globals;  
 而後在 yaf.c 中調用 ZEND_DECLARE_MODULE_GLOBALS 來實例化此結構體:

 

C代碼   收藏代碼
  1. ZEND_DECLARE_MODULE_GLOBALS(yaf);  
 展開後變成:
C代碼   收藏代碼
  1. zend_yaf_globals yaf_globals;  

 

這樣就能夠經過yaf_globals.base_uri來訪問base_uri這個全局變量了,以上說的是線程非安全的狀況,

在線程安全中,全局變量的定義又是另外一種狀況,這裏再也不展開。總之在線程安全和非安全中,對全局變量

的定義和訪問均不同,對於全局變量的訪問,PHP的擴展定義一個宏,用來訪問全局變量:

php_yaf.h

C代碼   收藏代碼
  1. #ifdef ZTS  
  2. #define YAF_G(v) TSRMG(yaf_globals_id, zend_yaf_globals *, v)  
  3. #else  
  4. #define YAF_G(v) (yaf_globals.v)  
  5. #endif  
4.3.3 全局變量的初始化

 

Yaf在Request init階段初始化全局變量:

 

C代碼   收藏代碼
  1. PHP_RINIT_FUNCTION(yaf)  
  2. {  
  3.     php_printf("PHP_RINIT_FUNCTION...\n");  
  4.     YAF_G(running)          = 0;  
  5.     YAF_G(in_exception)     = 0;  
  6.     YAF_G(throw_exception)  = 1;  
  7.     YAF_G(catch_exception)  = 0;  
  8.     YAF_G(directory)        = NULL;  
  9.     YAF_G(bootstrap)        = NULL;  
  10.     YAF_G(local_library)    = NULL;  
  11.     YAF_G(local_namespaces) = NULL;  
  12.     YAF_G(modules)          = NULL;  
  13.     YAF_G(base_uri)         = NULL;  
  14.     YAF_G(view_directory)   = NULL;  
  15. #if ((PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION < 4))  
  16.     YAF_G(buffer)           = NULL;  
  17.     YAF_G(owrite_handler)   = NULL;  
  18.     YAF_G(buf_nesting)      = 0;  
  19. #endif  
  20.   
  21.     return SUCCESS;  
  22. }  

 

4.3.4  application配置的初始化

建立application實例的時候,必須傳入一個配置選項的array。

 

Php代碼   收藏代碼
  1. $app_conf['directory'] = '/home/work/yaf/sample_app  
  2. $app_conf['baseUri'] = '/';  
  3.   
  4. $config = array(  
  5.     "application" => $app_conf,  
  6. );  
  7.   
  8. // 生成yaf實例  
  9. $app = new Yaf_Application($config);  

 

因此,application配置的初始化在 Yaf_Application的構造函數中完成,yaf_application.c定義的構造函數

會調用yaf_application_parse_option函數來解析傳過來的參數,並完成初始化:

 

yaf_application.c

C代碼   收藏代碼
  1. static int yaf_application_parse_option(zval *options TSRMLS_DC) {  
  2.     HashTable   *conf;  
  3.     zval        **ppzval, **ppsval, *app;  
  4.   
  5.     conf = HASH_OF(options);  
  6.     if (zend_hash_find(conf, ZEND_STRS("application"), (void **)&ppzval) == FAILURE) {  
  7.         /* For back compatibilty */  
  8.         if (zend_hash_find(conf, ZEND_STRS("yaf"), (void **)&ppzval) == FAILURE) {  
  9.             yaf_trigger_error(YAF_ERR_TYPE_ERROR TSRMLS_CC, "%s""Expected an array of application configure");  
  10.             return FAILURE;  
  11.         }  
  12.     }  
  13.   
  14.     app = *ppzval;  
  15.     if (Z_TYPE_P(app) != IS_ARRAY) {  
  16.         yaf_trigger_error(YAF_ERR_TYPE_ERROR TSRMLS_CC, "%s""Expected an array of application configure");  
  17.         return FAILURE;  
  18.     }  
  19.   
  20.     if (zend_hash_find(Z_ARRVAL_P(app), ZEND_STRS("directory"), (void **)&ppzval) == FAILURE  
  21.             || Z_TYPE_PP(ppzval) != IS_STRING) {  
  22.         yaf_trigger_error(YAF_ERR_STARTUP_FAILED TSRMLS_CC, "%s""Expected a directory entry in application configures");  
  23.         return FAILURE;  
  24.     }  
  25.   
  26.     //解析出application.directory的配置項,使用YAF_G初始化對應的全局變量  
  27.     if (*(Z_STRVAL_PP(ppzval) + Z_STRLEN_PP(ppzval) - 1) == DEFAULT_SLASH) {  
  28.         YAF_G(directory) = estrndup(Z_STRVAL_PP(ppzval), Z_STRLEN_PP(ppzval) - 1);  
  29.     } else {  
  30.         YAF_G(directory) = estrndup(Z_STRVAL_PP(ppzval), Z_STRLEN_PP(ppzval));  
  31.     }  
  32.       
  33.     其它配置選項的初始化省略...  
  34. }  

 

 

4.4 加載框架類

 Yaf的一個優勢是全部的框架類,不須要編譯,在PHP啓動的時候加載, 並常駐內存。如何作到這點呢?

Yaf定義了一個YAF_STARTUP宏來加載類,加載類在 Module init階段完成:

C代碼   收藏代碼
  1. PHP_MINIT_FUNCTION(yaf)  
  2. {  
  3.     ...  
  4.     /* startup components */  
  5.     YAF_STARTUP(application);  
  6.     YAF_STARTUP(bootstrap);  
  7.     YAF_STARTUP(dispatcher);  
  8.     YAF_STARTUP(loader);  
  9.     YAF_STARTUP(request);  
  10.     YAF_STARTUP(response);  
  11.     YAF_STARTUP(controller);  
  12.     YAF_STARTUP(action);  
  13.     YAF_STARTUP(config);  
  14.     YAF_STARTUP(view);  
  15.     YAF_STARTUP(router);  
  16.     YAF_STARTUP(plugin);  
  17.     YAF_STARTUP(registry);  
  18.     YAF_STARTUP(session);  
  19.     YAF_STARTUP(exception);  
  20.   
  21.     return SUCCESS;  
  22. }  

 YAF_STARTUP接收一個參數,能夠理解爲類名,而後加載這個類。具體是怎麼實現的呢?

php_yaf.h

C代碼   收藏代碼
  1. #define YAF_STARTUP_FUNCTION(module)    ZEND_MINIT_FUNCTION(yaf_##module)  
  2. #define YAF_STARTUP(module)         ZEND_MINIT(yaf_##module)(INIT_FUNC_ARGS_PASSTHRU)  

能夠看到YAF_STARTUP_FUNCTION這個宏用來定義一個啓動函數,YAF_STARTUP調用此啓動函數完成類的加載。而後,每一個類都會使用YAF_STARTUP_FUNCTION定義其啓動函數,例如,咱們來看router類的啓動函數。

router.c

C代碼   收藏代碼
  1. /* 
  2.  * router類的啓動函數 
  3.  */  
  4. YAF_STARTUP_FUNCTION(router) {  
  5.     zend_class_entry ce;  
  6.     (void)yaf_route_route_arginfo; /* tricky, supress warning "defined but not used" */  
  7.   
  8.     //加載router類  
  9.     YAF_INIT_CLASS_ENTRY(ce, "Yaf_Router""Yaf\\Router", yaf_router_methods);  
  10.     yaf_router_ce = zend_register_internal_class_ex(&ce, NULL, NULL TSRMLS_CC);  
  11.   
  12.     yaf_router_ce->ce_flags |= ZEND_ACC_FINAL_CLASS;  
  13.   
  14.     //聲明router類的屬性  
  15.     zend_declare_property_null(yaf_router_ce, ZEND_STRL(YAF_ROUTER_PROPERTY_NAME_ROUTERS),       ZEND_ACC_PROTECTED TSRMLS_CC);  
  16.     zend_declare_property_null(yaf_router_ce, ZEND_STRL(YAF_ROUTER_PROPERTY_NAME_CURRENT_ROUTE), ZEND_ACC_PROTECTED TSRMLS_CC);  
  17.   
  18.     //繼續加載路由協議相關的類  
  19.     YAF_STARTUP(route);  
  20.     YAF_STARTUP(route_static);  
  21.     YAF_STARTUP(route_simple);  
  22.     YAF_STARTUP(route_supervar);  
  23.     YAF_STARTUP(route_rewrite);  
  24.     YAF_STARTUP(route_regex);  
  25.     YAF_STARTUP(route_map);  
  26.   
  27.     return SUCCESS;  
  28. }  

 當調用 YAF_STARTUP(router) 時,展開後等於調用 

ZEND_MINIT(yaf_router)(INIT_FUNC_ARGS_PASSTHRU),

繼續展開,ZEND_MINIT(yaf_router)獲得的是一個函數指針,恰好指向ZEND_MINIT_FUNCTION(yaf_router)

定義的函數,便是YAF_STARTUP_FUNCTION(router)定義的函數,因此

ZEND_MINIT(yaf_router)(INIT_FUNC_ARGS_PASSTHRU)至關於調用router.c中YAF_STARTUP_FUNCTION(router)定義的函數。

 

注意在加載router類的時候,還會加載router類依賴的其它類,Yaf經過這種方式完成全部框架類的加載。

相關文章
相關標籤/搜索