1. 程式人生 > 程式設計 >PHP-Yaf執行流程-原始碼分析

PHP-Yaf執行流程-原始碼分析

PHP-Yaf執行流程-原始碼分析

介紹

Yaf框架是一個c語言編寫的PHP框架,是一個以PHP擴充套件形式提供的PHP開發框架,相比於一般的PHP框架, 它更快,更輕便,記憶體佔用率更低,就是本著對效能的追求,Yaf把框架中不易變的部分抽象出來,類如路由、自動載入、bootstrap、分發等,採用PHP擴充套件去實現,以此來保證效能。

Yaf優點

  • 用c語言開發的PHP框架,相比原生的PHP,幾乎不會帶來額外的效能開銷
  • 所有的框架類,不需要編譯,在PHP啟動的時候載入,並常駐記憶體.
  • 更快的執行速度,更少的記憶體佔用.
  • 靈巧的自動載入. 支援全域性和區域性兩種載入規則,方便類庫共享.

yaf缺點

  • 維護成本高,要維護PHP擴充套件,需要熟練C開發和Zend Api.
  • 目標使用者群小,現在國內很多中小型站都是使用虛擬主機,並不能隨意的給PHP新增擴充套件.
  • 不像其他框架一樣提供各種豐富功能的類庫和各種優雅的寫法,它只提供一個MVC的基本骨架.

Yaf框架執行流程圖

流程圖說明

在application目錄下有個Bootstrap.php檔案,這個就是圖中的第一個環節,如果存在Bootstrap()就會先執行該檔案,該檔案包含了一系列的初始化環節,並返回一個Yaf_Application物件,緊接著呼叫了它的run方法,run裡麵包含了圖中所有環節,run首先是呼叫路由,路由的主要目的其實就是找到controllers檔案,然後執行裡面的init和action方法,或者找到所有actions的地址然後載入,在去執行對應的execute方法,如果設定了autoRender在返回的時候會執行render方法,就是view自動渲染,圖中有六個雙橫線標出的環節,就是六個外掛方法,使用者可以自定義實現這幾個方法,然後Yaf框架會在圖中相應的步驟處呼叫對應的HOOK方法。

Yaf框架目錄結構

.
├── README.md
└── www
    ├── sdk // 三方 SDK 庫
    │   ├── README.md // 簡介
    ├── app
    │   ├── Bootstrap.php // 引導檔案
    │   ├── config  // 配置檔案
    │   │    ├── index.php
    │   ├── controllers // 控制器
    │   │    ├── index.php
    │   ├── library // 本地類庫
    │   │    ├── index.php
    │   ├── models  // model目錄
    │   │    ├── index.php
    │   ├── modules // 可以按模組來區分不同業務下的控制器層
    │   │    ├── index.php
    │   ├── plugins // 外掛目錄
    │   │    ├── index.php
    │   └── services
    │   │    ├── index.php
    ├── public
        ├── favicon.ico 
        ├── image // 公共圖片
        └── index.php // 入口檔案
複製程式碼

Yaf配置項說明

php.ini 配置項

選項名稱 預設值 說明
yaf.environ product 環境名稱,當用INI作為Yaf的配置檔案時,這個指明瞭Yaf將要在INI配置中讀取的節的名字
yaf.library NULL 全域性類庫的目錄路徑
yaf.cache_config 0 是否快取配置檔案(只針對INI配置檔案生效),開啟此選項可在複雜配置的情況下提高效能
yaf.name_suffix 1 在處理Controller,Action,Plugin,Model的時候,類名中關鍵資訊是否是字尾式,比如UserModel,而在字首模式下則是ModelUser
yaf.name_separator "" 在處理Controller,字首和名字之間的分隔符,預設為空,也就是UserPlugin,加入設定為"_",則判斷的依據就會變成:"User_Plugin",這個主要是為了相容ST已有的命名規範
yaf.forward_limit 5 forward最大巢狀深度
yaf.use_namespace 0 開啟的情況下,Yaf將會使用名稱空間方式註冊自己的類,比如Yaf_Application將會變成Yaf\Application
yaf.use_spl_autoload 0 開啟的情況下,Yaf在載入不成功的情況下,會繼續讓PHP的自動載入函式載入,從效能考慮,除非特殊情況,否則保持這個選項關閉
//php.ini
[Yaf]
yaf.library = "c:/huan"
yaf.name_suffix = 0
yaf.name_separator = "_"
yaf.environ = "product"
複製程式碼

application.ini 配置項

選項名稱 預設值 說明
application.ext php PHP指令碼的副檔名
application.bootstrap Bootstrapplication.php Bootstrap路徑(絕對路徑)
application.library application.directory + "/library" 本地(自身)類庫的絕對目錄地址
application.baseUri NULL 在路由中,需要忽略的路徑字首,一般不需要設定,Yaf會自動判斷.
application.dispatcher.defaultModule index 預設的模組
application.dispatcher.throwException True 在出錯的時候,是否丟擲異常
application.dispatcher.catchException False 是否使用預設的異常捕獲Controller,如果開啟,在有未捕獲的異常的時候,控制權會交給ErrorController的errorAction方法,可以通過$request->getException()獲得此異常物件
application.dispatcher.defaultController index 預設的控制器
application.dispatcher.defaultAction index 預設的動作
application.view.ext phtml 檢視模板副檔名
application.modules modules 宣告存在的模組名,請注意,如果你要定義這個值,一定要定義Index Module
//application.ini
[mysql]
mysql.master.user_name = word
mysql.master.pass_word = 1234
mysql.slave.user_name = word
mysql.slave.pass_word = 1234
[database : mysql]
database.master.host = 127.0.0.1
database.slave.host  = 127.0.0.2,127.0.0.3
[product : database]
yaf.directory = APP_PATH "/app/"
yaf.libray = APP_PATH "/libray/
複製程式碼

可以看到在application.ini配置檔案裡面除了配置框架本身的配置項,還可以新增一些我們自定義的配置項,同時支援繼承配置功能,在框架啟動的時候會根據yaf.environ設定的節點名字去讀取.

配置檔案解析完畢後讀取到記憶體的狀態

Yaf_Config_Ini Object
(
    [_config:protected] => Array
    (
        [mysql] => Array
            (
            [master] => Array
                (
                    [user_name] => word
                )
            [slave] => Array
                (
                    [user_name] => word
                )
            )

        [database] => Array
            (
                [master] => Array
                    (
                        [host] => 127.0.0.1
                    )

                [slave] => Array
                    (
                        [host] => 127.0.0.2,127.0.0.3
                    )

            )

        [yaf] => Array
            (
                [directory] => C:\huan\apache\htdocs\yaf/app/
                [libray] => C:\huan\apache\htdocs\yaf/libray/
            )

    )
		[_readonly:protected] => 1
)
複製程式碼

呼叫Yaf框架

//入口檔案index.php
define("APP_PATH",'/home/test/yaf');
$app  = new Yaf_Application(APP_PATH . "/conf/application.ini");
$app->bootstrap()->run();
複製程式碼

Bootstrap.php 類檔案

class Bootstrap extends Yaf_Bootstrap_Abstract{

        public function _initConfig(Yaf_Dispatcher $dispatcher) {
                //存放全域性資料
                $config = Yaf_Application::app()->getConfig();
                Yaf_Registry::set("config",$config);
                //讀取配置
                $dispatcher->getConfig()->get('database')->master->host
                 //關閉自動渲染
                $dispatcher->autoRender(false);
                //設定自定的模板引擎類如smarty
                $dispatcher->setView( Yaf_View_Interface  $request );
        }

        public function _initDefaultName(Yaf_Dispatcher $dispatcher) {
                $dispatcher->setDefaultModule("Index")->setDefaultController("Index")->setDefaultAction("index");
        }
       
        public function _initPlugin(Yaf_Dispatcher $dispatcher){
               //註冊外掛
               $objPlugin = new  Test_Plugin();
               $dispatcher->registerPlugin($objPlugin);
        }

        public function _initRegistLocalLib(Yaf_Dispatcher $dispatcher){
              //註冊本地類字首,是的對於以這些字首開頭的本地類,都從本地類庫路徑中載入.
              Yaf_Loader::getInstance()->registerLocalNamespace(array('Foo','Msn'));
        }
}
複製程式碼

Plugin.php 外掛類檔案

  class Test_Plugin extends Yaf_Plugin_Abstract {

       public function routerStartup(Yaf_Request_Abstract $request,Yaf_Response_Abstract $response) {
       }

       public function routerShutdown(Yaf_Request_Abstract $request,Yaf_Response_Abstract $response) {
       }
  }

複製程式碼

Controller.php 類檔案

class Index_Controller extends Yaf_Controller_Abstract {
   
   //第二種action實現方式
   public $actions = array(
        'one' => 'actions/index/One.php','two' => 'actions/index/Two.php',);
   
   //初始化方法
   public function ini(){

   }

   //第一種action實現方式
   public function index_Action() { 
       $this->getView()->assign("content","Hello World");
       $this->getView()->display();
   }
}

複製程式碼

Action.php 類檔案

  Class One_Action extends Yaf_Action_Abstract{
         public function execute(){

         }
  }
複製程式碼

Model 類檔案

    class Base_Model {
          public function getDataList(){
          }
    }
複製程式碼

注意: Yaf並沒有實現Model層,需要自己實現或者呼叫現成的Model庫.

擴充套件-核心功能模組實現

擴充套件 Yaf_Application 類註冊

YAF_STARTUP_FUNCTION(application) {  //巨集替換一下 ZEND_MINIT_FUNCTION(yaf_##module)
    //PHP核心中對PHP類的實現是通過zend_class_entry結構體實現的
    zend_class_entry ce;
    //相當於對ce初始化,指定一個類名稱 Ap_Application
    //指定類的成員方法列表 ap_application_methods 結構體陣列指標
    YAF_INIT_CLASS_ENTRY(ce,"Yaf_Application","Yaf\\Application",yaf_application_methods);
    //向PHP註冊類,PHP中由class_table維護全域性的類陣列
    //可以簡單理解為把類新增到這個陣列中,這樣就可以在
    //PHP中找到這個類了,核心中有一組類似的註冊函式
    //用來註冊介面、類、子類、介面實現、抽象類等
    yaf_application_ce = zend_register_internal_class_ex(&ce,NULL,NULL TSRMLS_CC);
    //指定類的屬性
    yaf_application_ce->ce_flags |= ZEND_ACC_FINAL_CLASS;
    //設定類內部的一些變數和屬性
    zend_declare_property_null(yaf_application_ce,ZEND_STRL(YAF_APPLICATION_PROPERTY_NAME_CONFIG),ZEND_ACC_PROTECTED TSRMLS_CC);
    zend_declare_property_null(yaf_application_ce,ZEND_STRL(YAF_APPLICATION_PROPERTY_NAME_DISPATCHER),ZEND_STRL(YAF_APPLICATION_PROPERTY_NAME_APP),ZEND_ACC_STATIC | ZEND_ACC_PROTECTED TSRMLS_CC);
    zend_declare_property_null(yaf_application_ce,ZEND_STRL(YAF_APPLICATION_PROPERTY_NAME_MODULES),ZEND_ACC_PROTECTED TSRMLS_CC);

    zend_declare_property_bool(yaf_application_ce,ZEND_STRL(YAF_APPLICATION_PROPERTY_NAME_RUN),0,ZEND_ACC_PROTECTED TSRMLS_CC);
    zend_declare_property_string(yaf_application_ce,ZEND_STRL(YAF_APPLICATION_PROPERTY_NAME_ENV),YAF_G(environ),ZEND_ACC_PROTECTED TSRMLS_CC);

    zend_declare_property_long(yaf_application_ce,ZEND_STRL(YAF_APPLICATION_PROPERTY_NAME_ERRNO),ZEND_STRL(YAF_APPLICATION_PROPERTY_NAME_ERRMSG),"",ZEND_ACC_PROTECTED TSRMLS_CC);
    //核心中有一組這樣的屬性標記來指定類和變數的性質
    /* method flags (types) 
       #define ZEND_ACC_STATIC            0x01
       #define ZEND_ACC_ABSTRACT        0x02
       #define ZEND_ACC_FINAL            0x04
       #define ZEND_ACC_IMPLEMENTED_ABSTRACT        0x08
       class flags (types) 
       #define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS    0x10
       #define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS    0x20
       #define ZEND_ACC_FINAL_CLASS                0x40 
       #define ZEND_ACC_INTERFACE                    0x80 */
    return SUCCESS;
}

//類成員函式的宣告
zend_function_entry yaf_application_methods[] = {
    PHP_ME(yaf_application,__construct,yaf_application_construct_arginfo,ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
    PHP_ME(yaf_application,run,yaf_application_run_arginfo,ZEND_ACC_PUBLIC)
    PHP_ME(yaf_application,execute,yaf_application_execute_arginfo,app,yaf_application_app_arginfo,ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
    YAF_ME(yaf_application_environ,"environ",yaf_application_environ_arginfo,bootstrap,yaf_application_bootstrap_arginfo,getConfig,yaf_application_getconfig_arginfo,getModules,yaf_application_getmodule_arginfo,getDispatcher,yaf_application_getdispatch_arginfo,setAppDirectory,yaf_application_setappdir_arginfo,getAppDirectory,yaf_application_void_arginfo,getLastErrorNo,getLastErrorMsg,clearLastError,__destruct,ZEND_ACC_PUBLIC | ZEND_ACC_DTOR)
    PHP_ME(yaf_application,__clone,ZEND_ACC_PRIVATE | ZEND_ACC_CLONE)
    PHP_ME(yaf_application,__sleep,ZEND_ACC_PRIVATE)
    PHP_ME(yaf_application,__wakeup,ZEND_ACC_PRIVATE)
    {NULL,NULL}
};

複製程式碼

上面這個就是在擴充套件裡面註冊一個類的實現,Yaf其他的類檔案在註冊類的時候也幾乎和上面方式一致,具體看下原始碼就可以了,下面將從例項化一個Yaf_Application類開始分析。

擴充套件 Yaf_Application 類建構函式 __construct 實現

PHP_METHOD(yaf_application,__construct) {
    yaf_config_t        *zconfig;
    yaf_request_t       *request;
    yaf_dispatcher_t    *zdispatcher;
    yaf_application_t   *app,*self;
    yaf_loader_t        *loader;
    zval            *config;
    zval            *section = NULL;
    //獲取yaf_application::_app變數值,預設為0
    app  = zend_read_static_property(yaf_application_ce,1 TSRMLS_CC);

#if PHP_YAF_DEBUG
    php_error_docref(NULL TSRMLS_CC,E_STRICT,"Yaf is running in debug mode");
#endif
    //如果app有值則報錯,也就是當前類只能被例項化一次
    if (!ZVAL_IS_NULL(app)) {
        yaf_trigger_error(YAF_ERR_STARTUP_FAILED TSRMLS_CC,"Only one application can be initialized");
        RETURN_FALSE;
    }
    //獲取當前的物件,也就是php裡面的$this
    self = getThis();
    //獲取引數,把配置檔案路徑或者配置陣列傳遞進來
    //section配置檔案中的節點名字,預設product
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"z|z",&config,&section) == FAILURE) {
        YAF_UNINITIALIZED_OBJECT(getThis());
        return;
    }
    
    if (!section || Z_TYPE_P(section) != IS_STRING || !Z_STRLEN_P(section)) {
        MAKE_STD_ZVAL(section);
        ZVAL_STRING(section,0);
        //獲取config物件
        zconfig = yaf_config_instance(NULL,config,section TSRMLS_CC);
        efree(section);
    } else {
        //獲取config物件
        zconfig = yaf_config_instance(NULL,section TSRMLS_CC);
    }
    //解析配置檔案,並將配置資訊儲存在zconfig物件中
    if  (zconfig == NULL
            || Z_TYPE_P(zconfig) != IS_OBJECT
            || !instanceof_function(Z_OBJCE_P(zconfig),yaf_config_ce TSRMLS_CC)
            || yaf_application_parse_option(zend_read_property(yaf_config_ce,zconfig,ZEND_STRL(YAF_CONFIG_PROPERT_NAME),1 TSRMLS_CC) TSRMLS_CC) == FAILURE) {
        YAF_UNINITIALIZED_OBJECT(getThis());
        yaf_trigger_error(YAF_ERR_STARTUP_FAILED TSRMLS_CC,"Initialization of application config failed");
        RETURN_FALSE;
    }
    //獲取 request 物件,請求的資訊儲存在裡面
    request = yaf_request_instance(NULL,YAF_G(base_uri) TSRMLS_CC);
    if (YAF_G(base_uri)) {
        efree(YAF_G(base_uri));
        YAF_G(base_uri) = NULL;
    }

    if (!request) {
        YAF_UNINITIALIZED_OBJECT(getThis());
        yaf_trigger_error(YAF_ERR_STARTUP_FAILED TSRMLS_CC,"Initialization of request failed");
        RETURN_FALSE;
    }
    //獲取dispatcher物件,主要在裡面呼叫外掛、路由、分發等
    zdispatcher = yaf_dispatcher_instance(NULL TSRMLS_CC);
    if (NULL == zdispatcher
            || Z_TYPE_P(zdispatcher) != IS_OBJECT
            || !instanceof_function(Z_OBJCE_P(zdispatcher),yaf_dispatcher_ce TSRMLS_CC)) {
        YAF_UNINITIALIZED_OBJECT(getThis());
        yaf_trigger_error(YAF_ERR_STARTUP_FAILED TSRMLS_CC,"Instantiation of application dispatcher failed");
        RETURN_FALSE;
    }
    //把request物件儲存在zdispatcher對像屬性中
    yaf_dispatcher_set_request(zdispatcher,request TSRMLS_CC);
    //儲存zconfig物件到當前yaf_application物件屬性中
    zend_update_property(yaf_application_ce,self,zconfig TSRMLS_CC);
    //儲存zdispatcher物件到當前yaf_application物件屬性中
    zend_update_property(yaf_application_ce,zdispatcher TSRMLS_CC);

    zval_ptr_dtor(&request);
    zval_ptr_dtor(&zdispatcher);
    zval_ptr_dtor(&zconfig);
    //是否指定本地類庫地址
    if (YAF_G(local_library)) {
        //獲取一個load載入類,並同時向核心註冊一個自動載入器__autoload
        //之後在程式碼中new class()都會呼叫擴充套件中註冊的自動載入器
        loader = yaf_loader_instance(NULL,YAF_G(local_library),strlen(YAF_G(global_library))? YAF_G(global_library) : NULL TSRMLS_CC);
        efree(YAF_G(local_library));
        YAF_G(local_library) = NULL;
    } else {
        char *local_library;
        //獲取一個預設的本地類庫地址
        spprintf(&local_library,"%s%c%s",YAF_G(directory),DEFAULT_SLASH,YAF_LIBRARY_DIRECTORY_NAME);
        //同上面解釋一致
        loader = yaf_loader_instance(NULL,local_library,strlen(YAF_G(global_library))? YAF_G(global_library) : NULL TSRMLS_CC);
        efree(local_library);
    }

    if (!loader) {
        YAF_UNINITIALIZED_OBJECT(getThis());
        yaf_trigger_error(YAF_ERR_STARTUP_FAILED TSRMLS_CC,"Initialization of application auto loader failed");
        RETURN_FALSE;
    }
       
    //賦值物件屬性
    zend_update_property_bool(yaf_application_ce,0 TSRMLS_CC);
    zend_update_property_string(yaf_application_ce,YAF_G(environ) TSRMLS_CC);
    
    //這個modules一定會存在,預設是Index
    if (YAF_G(modules))  {
        zend_update_property(yaf_application_ce,YAF_G(modules) TSRMLS_CC);
        Z_DELREF_P(YAF_G(modules));
        YAF_G(modules) = NULL;
    } else {
        zend_update_property_null(yaf_application_ce,ZEND_STRL(YAF_APPLICATION_PROPERTY_NAME_MODULES) TSRMLS_CC);
    }
    //yaf_application::_app 賦值成當前self物件
    zend_update_static_property(yaf_application_ce,self TSRMLS_CC);
}

複製程式碼

PHP_METHOD(Yaf_application,bootstrap) 擴充套件實現

PHP_METHOD(yaf_application,bootstrap) {
    char            *bootstrap_path;
    uint            len,retval = 1;
    zend_class_entry    **ce;
    yaf_application_t   *self = getThis();
    //判斷 Bootstrap 類是否存在,預設第一次肯定不存在
    //因為還沒有載入註冊進來
    if (zend_hash_find(EG(class_table),YAF_DEFAULT_BOOTSTRAP_LOWER,YAF_DEFAULT_BOOTSTRAP_LEN,(void **) &ce) != SUCCESS) {
        //判斷是否指定了bootstrap檔案路徑
        if (YAF_G(bootstrap)) {
            bootstrap_path  = estrdup(YAF_G(bootstrap));
            len = strlen(YAF_G(bootstrap));
        } else {
            //沒有指定則使用預設路徑
            len = spprintf(&bootstrap_path,"%s%c%s.%s",YAF_DEFAULT_BOOTSTRAP,YAF_G(ext));
        }
        //匯入bootstrap檔案,核心如果發現是類,則會自動註冊上
        if (!yaf_loader_import(bootstrap_path,len + 1,0 TSRMLS_CC)) {
            php_error_docref(NULL TSRMLS_CC,E_WARNING,"Couldn't find bootstrap file %s",bootstrap_path);
            retval = 0;
        //獲取 bootstrap 類
        } else if (zend_hash_find(EG(class_table),(void **) &ce) != SUCCESS)  {
            php_error_docref(NULL TSRMLS_CC,"Couldn't find class %s in %s",bootstrap_path);
            retval = 0;
        //判斷該類是否繼承Yaf_Bootstrap_Abstract
        } else if (!instanceof_function(*ce,yaf_bootstrap_ce TSRMLS_CC)) {
            php_error_docref(NULL TSRMLS_CC,"Expect a %s instance,%s give",yaf_bootstrap_ce->name,(*ce)->name);
            retval = 0;
        }

        efree(bootstrap_path);
    }

    if (!retval) {
        RETURN_FALSE;
    } else {
        zval            *bootstrap;
        HashTable       *methods;
        yaf_dispatcher_t    *dispatcher;
        //例項化一個bootstrap物件
        MAKE_STD_ZVAL(bootstrap);
        object_init_ex(bootstrap,*ce);
        //獲取dispatcher 物件
        dispatcher = zend_read_property(yaf_application_ce,self,1 TSRMLS_CC);
        //獲取bootstrap類裡面所有的方法,每個類都用一個函式表function_table來記錄
        //方法名和對應結構體指標
        methods = &((*ce)->function_table);
        //迴圈bootstrap類裡面所有的方法
        //核心中對array的實現是通過hash_table實現的,核心中會
        //看到到處使用hash_table的地方,可以簡單理解為陣列的操作
        for(zend_hash_internal_pointer_reset(methods);
                zend_hash_has_more_elements(methods) == SUCCESS;
                zend_hash_move_forward(methods)) {
            char *func;
            uint len;
            ulong idx;
            //獲取第一個方法名字
            zend_hash_get_current_key_ex(methods,&func,&len,&idx,NULL);
            /* cann't use ZEND_STRL in strncasecmp,it cause a compile failed in VS2009 */
            #define YAF_BOOTSTRAP_INITFUNC_PREFIX   "_init"
            //Bootstrap類實現了一系列的_init開頭的方法
            //這裡是比較函式func是否以_init開頭
            if (strncasecmp(func,YAF_BOOTSTRAP_INITFUNC_PREFIX,sizeof(YAF_BOOTSTRAP_INITFUNC_PREFIX)-1)) {
                continue;
            }
            //呼叫所有以_init開頭的函式,入參統一為dispatcher物件
            zend_call_method(&bootstrap,*ce,NULL,func,len - 1,1,dispatcher,NULL TSRMLS_CC);
            /** an uncaught exception threw in function call */
            if (EG(exception)) {
                zval_ptr_dtor(&bootstrap);
                RETURN_FALSE;
            }
        }

        zval_ptr_dtor(&bootstrap);
    }
    //最後會返回application物件自身
    RETVAL_ZVAL(self,0);
}

複製程式碼

PHP_METHOD(yaf_application,run) 擴充套件實現

PHP_METHOD(yaf_application,run) {
    zval *running;
    yaf_dispatcher_t  *dispatcher;
    yaf_response_t    *response;
    yaf_application_t *self = getThis();
        
    //獲取屬性值,預設第一次為0
    running = zend_read_property(yaf_application_ce,1 TSRMLS_CC);
    if (IS_BOOL == Z_TYPE_P(running)
            && Z_BVAL_P(running)) {
        yaf_trigger_error(YAF_ERR_STARTUP_FAILED TSRMLS_CC,"An application instance already run");
        RETURN_TRUE;
    }
    //賦值為1,可以看出來當前application物件中的run函式只能被呼叫一次
    ZVAL_BOOL(running,1);
    zend_update_property(yaf_application_ce,running TSRMLS_CC);
    //獲取dispatcher物件
    dispatcher = zend_read_property(yaf_application_ce,1 TSRMLS_CC);
    //進行路由分發等工作
    if ((response = yaf_dispatcher_dispatch(dispatcher TSRMLS_CC))) {
        RETURN_ZVAL(response,1,1);
    }

    RETURN_FALSE;
}

複製程式碼

yaf_dispatcher_dispatch() 方法裡面主要分兩部分,路由+分發,其中夾雜著一些外掛呼叫,就是上圖中雙橫線標出的環節,先看下官方對外掛的說明。

觸發順序 名稱 觸發時機
1 routerStartup 在路由之前觸發
2 routerShutdown 路由結束之後觸發
3 dispatchLoopStartup 分發迴圈開始之前被觸發
4 preDispatch 分發之前觸發
5 postDispatch 分發結束之後觸發
6 dispatchLoopShutdown 分發迴圈結束之後觸發

擴充套件中外掛方法名用巨集表示

#define YAF_PLUGIN_HOOK_ROUTESTARTUP                "routerstartup"
#define YAF_PLUGIN_HOOK_ROUTESHUTDOWN           "routershutdown"
#define YAF_PLUGIN_HOOK_LOOPSTARTUP             "dispatchloopstartup"
#define YAF_PLUGIN_HOOK_PREDISPATCH                 "predispatch"
#define YAF_PLUGIN_HOOK_POSTDISPATCH                "postdispatch"
#define YAF_PLUGIN_HOOK_LOOPSHUTDOWN                "dispatchloopshutdown"
#define YAF_PLUGIN_HOOK_PRERESPONSE             "preresponse"

複製程式碼

yaf_dispatcher_dispatch(yaf_dispatcher_t *dispatcher TSRMLS_DC)

yaf_response_t * yaf_dispatcher_dispatch(yaf_dispatcher_t *dispatcher TSRMLS_DC) {
    zval *return_response,*plugins,*view;
    yaf_response_t *response;
    yaf_request_t *request;
    uint nesting = YAF_G(forward_limit);
    //獲取response物件
    response = yaf_response_instance(NULL,sapi_module.name TSRMLS_CC);
    //獲取request物件
    request  = zend_read_property(yaf_dispatcher_ce,ZEND_STRL(YAF_DISPATCHER_PROPERTY_NAME_REQUEST),1 TSRMLS_CC);
    //獲取外掛陣列物件,這是一個陣列,裡面每個值都是一個外掛物件
    //但前提是我們建立外掛類,例項化之後,註冊進dispatcher物件裡
    plugins  = zend_read_property(yaf_dispatcher_ce,ZEND_STRL(YAF_DISPATCHER_PROPERTY_NAME_PLUGINS),1 TSRMLS_CC);
    //如果不是物件則報錯
    if (IS_OBJECT != Z_TYPE_P(request)) {
        yaf_trigger_error(YAF_ERR_TYPE_ERROR TSRMLS_CC,"Expect a %s instance",yaf_request_ce->name);
        zval_ptr_dtor(&response);
        return NULL;
    }

    /* route request */
    //是否已經路由過
    if (!yaf_request_is_routed(request TSRMLS_CC)) {
        //呼叫外掛routerstartup方法,如果註冊的情況
        YAF_PLUGIN_HANDLE(plugins,YAF_PLUGIN_HOOK_ROUTESTARTUP,request,response);
        //捕獲異常 Error_Controller,開啟的情況下,對應配置項為 catchException
        YAF_EXCEPTION_HANDLE(dispatcher,response);
        //進行路由,就是找到 controller、action、model
        if (!yaf_dispatcher_route(dispatcher,request TSRMLS_CC)) {
           //丟擲異常
            yaf_trigger_error(YAF_ERR_ROUTE_FAILED TSRMLS_CC,"Routing request failed");
            //捕獲異常 Error_Controller
            YAF_EXCEPTION_HANDLE_NORET(dispatcher,response);
            zval_ptr_dtor(&response);
            return NULL;
        }
        //設定Controller、action、model
        yaf_dispatcher_fix_default(dispatcher,request TSRMLS_CC);
        //呼叫外掛routerShutdown方法
        YAF_PLUGIN_HANDLE(plugins,YAF_PLUGIN_HOOK_ROUTESHUTDOWN,response);
        //捕獲異常
        YAF_EXCEPTION_HANDLE(dispatcher,response);
        //已經路由完畢標識
        (void)yaf_request_set_routed(request,1 TSRMLS_CC);
    } else {
        //設定Controller、action、model
        yaf_dispatcher_fix_default(dispatcher,request TSRMLS_CC);
    }
    //呼叫外掛dispatchLoopStartup方法
    YAF_PLUGIN_HANDLE(plugins,YAF_PLUGIN_HOOK_LOOPSTARTUP,response);
    //捕獲異常
    YAF_EXCEPTION_HANDLE(dispatcher,response);
    //獲取view物件
    view = yaf_dispatcher_init_view(dispatcher,NULL TSRMLS_CC);
    if (!view) {
        return NULL;
    }

    do {
        //呼叫外掛preDispatch方法
        YAF_PLUGIN_HANDLE(plugins,YAF_PLUGIN_HOOK_PREDISPATCH,response);
        //開始分發
        if (!yaf_dispatcher_handle(dispatcher,response,view TSRMLS_CC)) {
            YAF_EXCEPTION_HANDLE(dispatcher,response);
            zval_ptr_dtor(&response);
            return NULL;
        }
        yaf_dispatcher_fix_default(dispatcher,request TSRMLS_CC);
        //呼叫外掛postDispatch方法
        YAF_PLUGIN_HANDLE(plugins,YAF_PLUGIN_HOOK_POSTDISPATCH,response);
        //這個就是控制分發次數,配置項中的forward_limit
    } while (--nesting > 0 && !yaf_request_is_dispatched(request TSRMLS_CC));
    //呼叫外掛dispatchLoopShutdown方法
    YAF_PLUGIN_HANDLE(plugins,YAF_PLUGIN_HOOK_LOOPSHUTDOWN,response);
    //如果分發次數全部耗盡,且還有異常的話
    if (0 == nesting && !yaf_request_is_dispatched(request TSRMLS_CC)) {
        yaf_trigger_error(YAF_ERR_DISPATCH_FAILED TSRMLS_CC,"The max dispatch nesting %ld was reached",YAF_G(forward_limit));
        YAF_EXCEPTION_HANDLE_NORET(dispatcher,response);
        zval_ptr_dtor(&response);
        return NULL;
    }
    //最後返回一個 response 物件
    return_response = zend_read_property(yaf_dispatcher_ce,ZEND_STRL(YAF_DISPATCHER_PROPERTY_NAME_RETURN),1 TSRMLS_CC);

    if (!Z_BVAL_P(return_response)) {
        (void)yaf_response_send(response TSRMLS_CC);
        yaf_response_clear_body(response,0 TSRMLS_CC);
    }

    return response;
}

複製程式碼

yaf_dispatcher_route() 路由入口

int yaf_dispatcher_route(yaf_dispatcher_t *dispatcher,yaf_request_t *request TSRMLS_DC) {
    zend_class_entry *router_ce;
    //獲取路由器物件,dispatcher初始化時會建立內建路由器
    yaf_router_t *router = zend_read_property(yaf_dispatcher_ce,ZEND_STRL(YAF_DISPATCHER_PROPERTY_NAME_ROUTER),1 TSRMLS_CC);
    if (IS_OBJECT == Z_TYPE_P(router)) {
        //是否內建的路由器
        if ((router_ce = Z_OBJCE_P(router)) == yaf_router_ce) {
            //執行路由器裡面的路由協議
            yaf_router_route(router,request TSRMLS_CC);
        } else {
            //自定義路由器
            /* user custom router */
            zval *ret = zend_call_method_with_1_params(&router,router_ce,"route",&ret,request);
            if (Z_TYPE_P(ret) == IS_BOOL && Z_BVAL_P(ret) == 0) {
                yaf_trigger_error(YAF_ERR_ROUTE_FAILED TSRMLS_CC,"Routing request faild");
                return 0;
            }
        }
        return 1;
    }
    return 0;
}

複製程式碼

這段程式碼是路由環節的入口,dispatcher初始化時會建立內建路由器,這裡只涉及路由器概念,上面的自定義並不是自定義路由協議,而是你可以重新寫一個路由器,我們通常在專案中自定義路由協議就可以了,沒有必要自己實現一個路由器。而且框架中其實也是寫死了內建路由器,沒有給你set自定義路由器的介面。

yaf_router_route() 執行路由

int yaf_router_route(yaf_router_t *router,yaf_request_t *request TSRMLS_DC) {
    zval        *routers,*ret;
    yaf_route_t **route;
    HashTable   *ht;
    //獲取路由協議,可以新增多層路由協議,類似於多重外掛
    //通過addRoute方法向路由器註冊自己的路由協議,預設的
    //路由協議是Yaf_Route_Static
    routers = zend_read_property(yaf_router_ce,router,ZEND_STRL(YAF_ROUTER_PROPERTY_NAME_ROUTES),1 TSRMLS_CC);

    ht = Z_ARRVAL_P(routers);
    //for迴圈依次呼叫路由協議的route方法,成功則記下當前
    //生效的這個路由協議的索引位置,並設定request為已路由
    //不成功則繼續呼叫下一個路由協議。
    for(zend_hash_internal_pointer_end(ht);
            zend_hash_has_more_elements(ht) == SUCCESS;
            zend_hash_move_backwards(ht)) {

        if (zend_hash_get_current_data(ht,(void**)&route) == FAILURE) {
            continue;
        }
        //呼叫路由協議物件的route方法
        zend_call_method_with_1_params(route,Z_OBJCE_PP(route),request);

        if (IS_BOOL != Z_TYPE_P(ret) || !Z_BVAL_P(ret)) {
            zval_ptr_dtor(&ret);
            continue;
        } else {
            char *key;
            uint len = 0;
            ulong idx = 0;
            //記錄當前路由協議的索引
            switch(zend_hash_get_current_key_ex(ht,&key,NULL)) {
                case HASH_KEY_IS_LONG:
                    zend_update_property_long(yaf_router_ce,ZEND_STRL(YAF_ROUTER_PROPERTY_NAME_CURRENT_ROUTE),idx TSRMLS_CC);
                    break;
                case HASH_KEY_IS_STRING:
                    if (len) {
                        zend_update_property_string(yaf_router_ce,key TSRMLS_CC);
                    }
                    break;
            }
            //設定為已路由
            yaf_request_set_routed(request,1 TSRMLS_CC);
            zval_ptr_dtor(&ret);
            break;
        }
    }
    return 1;
}

複製程式碼

內建的路由協議

上面幾種路由協議原始碼列表

yaf_route_static.c
yaf_route_simple.c
yaf_route_supervar.c
yaf_route_rewrite.c
yaf_route_regex.c
yaf_route_map.c
複製程式碼

無路是哪個路由協議最後功能都是為了設定module,controller,action的名稱

yaf_dispatcher_handle() 開始分發

//只看下主邏輯 
int yaf_dispatcher_handle(yaf_dispatcher_t *dispatcher,yaf_request_t *request,yaf_response_t *response,yaf_view_t *view TSRMLS_DC) {

    zend_class_entry *request_ce;
    //程式碼根目錄
    char *app_dir = YAF_G(directory);

    request_ce = Z_OBJCE_P(request);
    //代表request開始,如果出現異常,則會在把當前狀態改成0
    //在分發迴圈while會判斷這個值,如果為0則代表之前分發中
    //存在異常所以保證在分發forward_limit次數內再繼續分發
    //如果等於1則代表沒有異常情況,分發完畢.
    yaf_request_set_dispatched(request,1 TSRMLS_CC);
    if (!app_dir) {
        yaf_trigger_error(YAF_ERR_STARTUP_FAILED TSRMLS_CC,"%s requires %s(which set the application.directory) to be initialized first",yaf_dispatcher_ce->name,yaf_application_ce->name);
        return 0;
    } else {
        int is_def_module = 0;
        /* int is_def_ctr = 0; */
        zval *module,*controller,*dmodule,/* *dcontroller,*/ *instantly_flush;
        zend_class_entry *ce;
        yaf_controller_t *executor;
        zend_function    *fptr;
        //獲取module
        module      = zend_read_property(request_ce,ZEND_STRL(YAF_REQUEST_PROPERTY_NAME_MODULE),1 TSRMLS_CC);
        //獲取controller
        controller  = zend_read_property(request_ce,ZEND_STRL(YAF_REQUEST_PROPERTY_NAME_CONTROLLER),1 TSRMLS_CC);
        //獲取預設module預設為Index
        dmodule     = zend_read_property(yaf_dispatcher_ce,ZEND_STRL(YAF_DISPATCHER_PROPERTY_NAME_MODULE),1 TSRMLS_CC);

        if (Z_TYPE_P(module) != IS_STRING
                || !Z_STRLEN_P(module)) {
            yaf_trigger_error(YAF_ERR_DISPATCH_FAILED TSRMLS_CC,"Unexcepted a empty module name");
            return 0;
        //判斷當前請求的model是否在modules裡面,預設情況modules裡面也是Index
        //如果自定義其他的model,就需要新增在modules這個選項裡面,新增的無論是
        //什麼model都需要存在Index這個model,類似於:Index,Admin,Main這樣,不添
        //加的話會提示錯誤.
        } else if (!yaf_application_is_module_name(Z_STRVAL_P(module),Z_STRLEN_P(module) TSRMLS_CC)) {
            yaf_trigger_error(YAF_ERR_NOTFOUND_MODULE TSRMLS_CC,"There is no module %s",Z_STRVAL_P(module));
            return 0;
        }

        if (Z_TYPE_P(controller) != IS_STRING
                || !Z_STRLEN_P(controller)) {
            yaf_trigger_error(YAF_ERR_DISPATCH_FAILED TSRMLS_CC,"Unexcepted a empty controller name");
            return 0;
        }
        //判斷當前的model是否等於預設的model
        //如果等於就去預設的Controllers目錄下找
        //控制器,如果不等於這個值就為0則就去
        //modules/model/controllers/下面找控制器
        //後面會有體現
        if (strncasecmp(Z_STRVAL_P(dmodule),Z_STRVAL_P(module),Z_STRLEN_P(module)) == 0) {
            is_def_module = 1;
        }
        //找到對應的controller類
        ce = yaf_dispatcher_get_controller(app_dir,Z_STRVAL_P(controller),Z_STRLEN_P(controller),is_def_module TSRMLS_CC);
        if (!ce) {
            return 0;
        } else {
            zval  *action,*render,*ret = NULL;
            char  *action_lower,*func_name,*view_dir;
            uint  func_name_len;

            yaf_controller_t *icontroller;
            //例項化controoller物件
            MAKE_STD_ZVAL(icontroller);
            object_init_ex(icontroller,ce);

            yaf_controller_construct(ce,icontroller,view,NULL TSRMLS_CC);
            if (EG(exception)) {
                zval_ptr_dtor(&icontroller);
                return 0;
            }
        
            //獲取view路徑,如果這個is_def_module為1
            //則取預設的views目錄,如果為0,則取 modules/model/views 這個目錄
            if (is_def_module) {
                spprintf(&view_dir,app_dir,"views");
            } else {
                spprintf(&view_dir,"%s%c%s%c%s%c%s","modules","views");
            }

            if (YAF_G(view_directory)) {
                efree(YAF_G(view_directory));
            }
            YAF_G(view_directory) = view_dir;

            zend_update_property(ce,ZEND_STRL(YAF_CONTROLLER_PROPERTY_NAME_NAME),controller TSRMLS_CC);
            
            //獲取action
            action       = zend_read_property(request_ce,ZEND_STRL(YAF_REQUEST_PROPERTY_NAME_ACTION),1 TSRMLS_CC);
            action_lower = zend_str_tolower_dup(Z_STRVAL_P(action),Z_STRLEN_P(action));

            /* because the action might call the forward to override the old action */
            Z_ADDREF_P(action);
            //拼接一個action函式名
            func_name_len = spprintf(&func_name,"%s%s",action_lower,"action");
            efree(action_lower);
            //判斷在controller物件裡面是否有這個action函式,如 indexaction
            if (zend_hash_find(&((ce)->function_table),func_name,func_name_len + 1,(void **)&fptr) == SUCCESS) {
                //省略......

                executor = icontroller;

                //如果存在這個action函式則呼叫
                zend_call_method(&icontroller,ce,func_name_len,NULL TSRMLS_CC);
            
                efree(func_name);

                //省略......

            //如果不存在這個action函式則按第二種action方式去獲
            //取看看在這個controller裡面是否存在actions變數,因
            //為有可能在這個actions陣列變數裡面記錄所有的action
            //類方法地址
            } else if ((ce = yaf_dispatcher_get_action(app_dir,is_def_module,Z_STRVAL_P(action),Z_STRLEN_P(action) TSRMLS_CC))
                    && (zend_hash_find(&(ce)->function_table,YAF_ACTION_EXECUTOR_NAME,sizeof(YAF_ACTION_EXECUTOR_NAME),(void **)&fptr) == SUCCESS)) {
                //省略......

                //例項化這個Action類
                MAKE_STD_ZVAL(iaction);
                object_init_ex(iaction,ce);
                executor = iaction;
                //省略......
                
                //呼叫這個Action類裡面的execute方法,可以看到這個方法是固定的
                zend_call_method_with_0_params(&iaction,"execute",&ret);
                
                //省略......
            } else {
                efree(func_name);
                zval_ptr_dtor(&icontroller);
                return 0;
            }
            //下面這部分就是檢視view自動渲染部分,不在詳細分析了
            if (executor) {
                int auto_render = 1;
                render = zend_read_property(ce,executor,ZEND_STRL(YAF_CONTROLLER_PROPERTY_NAME_RENDER),1 TSRMLS_CC);
                instantly_flush = zend_read_property(yaf_dispatcher_ce,ZEND_STRL(YAF_DISPATCHER_PROPERTY_NAME_FLUSH),1 TSRMLS_CC);
                if (render == EG(uninitialized_zval_ptr)) {
                    render = zend_read_property(yaf_dispatcher_ce,ZEND_STRL(YAF_DISPATCHER_PROPERTY_NAME_RENDER),1 TSRMLS_CC);
                    auto_render = Z_BVAL_P(render);
                } else if (Z_TYPE_P(render) <= IS_BOOL && !Z_BVAL_P(render)) {
                    auto_render = 0;
                }
                //是否自動渲染view
                if (auto_render) {
                    ret = NULL;
                    if (!Z_BVAL_P(instantly_flush)) {
                        zend_call_method_with_1_params(&executor,"render",action);
                        zval_ptr_dtor(&executor);

                        if (!ret) {
                            zval_ptr_dtor(&action);
                            return 0;
                        } else if (IS_BOOL == Z_TYPE_P(ret) && !Z_BVAL_P(ret)) {
                            zval_ptr_dtor(&ret);
                            zval_ptr_dtor(&action);
                            return 0;
                        }

                        if (Z_TYPE_P(ret) == IS_STRING && Z_STRLEN_P(ret)) {
                            yaf_response_alter_body(response,Z_STRVAL_P(ret),Z_STRLEN_P(ret),YAF_RESPONSE_APPEND  TSRMLS_CC);
                        } 

                        zval_ptr_dtor(&ret);
                    } else {
                        zend_call_method_with_1_params(&executor,"display",action);
                        zval_ptr_dtor(&executor);

                        if (!ret) {
                            zval_ptr_dtor(&action);
                            return 0;
                        }

                        if ((Z_TYPE_P(ret) == IS_BOOL && !Z_BVAL_P(ret))) {
                            zval_ptr_dtor(&ret);
                            zval_ptr_dtor(&action);
                            return 0;
                        }
                        zval_ptr_dtor(&ret);
                    }
                } else {
                    zval_ptr_dtor(&executor);
                }
            }
            zval_ptr_dtor(&action);
        }
        return 1;
    }
return 0;
}

複製程式碼

yaf_dispatcher_get_controller() 獲取 controller 類

zend_class_entry * yaf_dispatcher_get_controller(char* app_dir,char *module,char *controller,int len,int def_module TSRMLS_DC) {
    char     *directory     = NULL;
    int  directory_len  = 0;
        
    //這塊之前說過,如果def_module等於1走預設的路徑
    //如果等於0則走modules下的路徑
    if (def_module) {
        // directory = app_dir/controllers
        directory_len = spprintf(&directory,"%s%c%s",YAF_CONTROLLER_DIRECTORY_NAME);
    } else {
        // directory = app_dir/modules/model/controllers
        directory_len = spprintf(&directory,"%s%c%s%c%s%c%s",YAF_MODULE_DIRECTORY_NAME,module,YAF_CONTROLLER_DIRECTORY_NAME);
    }
     
    if (directory_len) {
        char *class         = NULL;
        char *class_lowercase   = NULL;
        int class_len       = 0;
        zend_class_entry **ce   = NULL;
        // 這裡根據配置區分字首模式還是字尾模式 
        // Controller_Index 或者 Index_Controller 
        // ControllerIndex 或者 IndexController 
        if (YAF_G(name_suffix)) {
            class_len = spprintf(&class,"%s%s%s",controller,YAF_G(name_separator),"Controller");
        } else {
            class_len = spprintf(&class,"Controller",controller);
        }
        //轉小寫
        class_lowercase = zend_str_tolower_dup(class,class_len);
       
        //是否存在這個Controller類
        if (zend_hash_find(EG(class_table),class_lowercase,class_len + 1,(void **)&ce) != SUCCESS) {
            //載入這個Controller類
            if (!yaf_internal_autoload(controller,len,&directory TSRMLS_CC)) {
                yaf_trigger_error(YAF_ERR_NOTFOUND_CONTROLLER TSRMLS_CC,"Failed opening controller script %s: %s",directory,strerror(errno));
                efree(class);
                efree(class_lowercase);
                efree(directory);
                return NULL;
            //獲取這個Controller類指標
            } else if (zend_hash_find(EG(class_table),(void **) &ce) != SUCCESS)  {
                yaf_trigger_error(YAF_ERR_AUTOLOAD_FAILED TSRMLS_CC,"Could not find class %s in controller script %s",class,directory);
                efree(class);
                efree(class_lowercase);
                efree(directory);
                return 0;
            //判斷是否繼承 Yaf_Controller_Abstract
            } else if (!instanceof_function(*ce,yaf_controller_ce TSRMLS_CC)) {
                yaf_trigger_error(YAF_ERR_TYPE_ERROR TSRMLS_CC,"Controller must be an instance of %s",yaf_controller_ce->name);
                efree(class);
                efree(class_lowercase);
                efree(directory);
                return 0;
            }
        }

        efree(class);
        efree(class_lowercase);
        efree(directory);

        return *ce;
    }

    return NULL;
}

複製程式碼

yaf_dispatcher_get_action() 獲取 action 類

zend_class_entry * yaf_dispatcher_get_action(char *app_dir,yaf_controller_t *controller,int def_module,char *action,int len TSRMLS_DC) {
    zval **ppaction,*actions_map;
    
    //獲取actions陣列
    actions_map = zend_read_property(Z_OBJCE_P(controller),ZEND_STRL(YAF_CONTROLLER_PROPERTY_NAME_ACTIONS),1 TSRMLS_CC);

    if (IS_ARRAY == Z_TYPE_P(actions_map)) {
        zend_class_entry **ce;
        uint  class_len;
        char *class_name,*class_lowercase;
        char *action_upper = estrndup(action,len);
        //將action名字首字母轉為大寫
        *(action_upper) = toupper(*action_upper);
        
        //前字尾模式
        //Index_Action 或 Action_Index
        //IndexAction 或 ActionIndex
        if (YAF_G(name_suffix)) {
            class_len = spprintf(&class_name,"%s%s%s",action_upper,"Action");
        } else {
            class_len = spprintf(&class_name,"Action",action_upper);
        }
       
        //類名轉換為小寫
        class_lowercase = zend_str_tolower_dup(class_name,class_len);
        //是否存在這個Action類
        if (zend_hash_find(EG(class_table),class_len + 1,(void **) &ce) == SUCCESS) {
            efree(action_upper);
            efree(class_lowercase);
            //是否繼承Yaf_Action_Abstract
            if (instanceof_function(*ce,yaf_action_ce TSRMLS_CC)) {
                efree(class_name);
                return *ce;
            } else {
                yaf_trigger_error(YAF_ERR_TYPE_ERROR TSRMLS_CC,"Action %s must extends from %s",class_name,yaf_action_ce->name);
                efree(class_name);
                return NULL;
            }
        }
        //在陣列中找到對應的key(action名稱)
        if (zend_hash_find(Z_ARRVAL_P(actions_map),action,len + 1,(void **)&ppaction) == SUCCESS) {
            char *action_path;
            uint action_path_len;
            //把值(後面的檔案路徑)賦給action_path
            //也就是得到了action類所在的檔案了
            action_path_len = spprintf(&action_path,Z_STRVAL_PP(ppaction));
            //匯入這個類檔案
            if (yaf_loader_import(action_path,action_path_len,0 TSRMLS_CC)) {
                //action類是否存在
                if (zend_hash_find(EG(class_table),(void **) &ce) == SUCCESS) {
                    efree(action_path);
                    efree(action_upper);
                    efree(class_lowercase);
                    //是否繼承Yaf_Action_Abstract
                    if (instanceof_function(*ce,yaf_action_ce TSRMLS_CC)) {
                        efree(class_name);
                        //返回Action類指標
                        return *ce;
                    } else {
                        yaf_trigger_error(YAF_ERR_TYPE_ERROR TSRMLS_CC,yaf_action_ce->name);
                        efree(class_name);
                    }

                } else {
                    yaf_trigger_error(YAF_ERR_NOTFOUND_ACTION TSRMLS_CC,"Could not find action %s in %s",action_path);
                }

                efree(action_path);
                efree(action_upper);
                efree(class_name);
                efree(class_lowercase);

            } else {
                yaf_trigger_error(YAF_ERR_NOTFOUND_ACTION TSRMLS_CC,"Failed opening action script %s: %s",action_path,strerror(errno));
                efree(action_path);
            }
        } else {
            yaf_trigger_error(YAF_ERR_NOTFOUND_ACTION TSRMLS_CC,"There is no method %s%s in %s::$%s",Z_OBJCE_P(controller)->name,YAF_CONTROLLER_PROPERTY_NAME_ACTIONS);
        } 
     } else if (YAF_G(st_compatible)) {
            //省略.....

            //這部分不說了,大概就是在 actions 裡面沒有找到這個action的路徑
            //那麼就嘗試自己拼接路徑去載入

            if (def_module) {
                spprintf(&directory,"actions");
            } else {
                spprintf(&directory,"actions");
            }

           //省略.....
     }
}

複製程式碼

上面也看到action的路徑是按照你所填寫的對映中地址載入,但是類名卻是action的名稱拼接的,所以雖然類檔案不需要按照Yaf的標準路徑設定,但是類名必須和action一致,在這個環節可能會因為action的特殊性出現找不到類的問題.

Yaf 自動載入

在例項化 application 類的時候,內部會自動例項化一個 Yaf_Loader 物件,同時往核心註冊了一個自動載入器 autoload 這裡註冊自動載入器也是用核心提供的 spl_autoload_register

yaf_loader_register() 註冊自動載入器

int yaf_loader_register(yaf_loader_t *loader TSRMLS_DC) {
    zval *autoload,*method,*function,*ret = NULL;
    zval **params[1] = {&autoload};
     
    //設定autoload為一個陣列
    MAKE_STD_ZVAL(autoload);
    array_init(autoload);

#define YAF_AUTOLOAD_FUNC_NAME  "autoload"
#define YAF_SPL_AUTOLOAD_REGISTER_NAME "spl_autoload_register"

    //設定method = autoload
    MAKE_STD_ZVAL(method);
    ZVAL_STRING(method,YAF_AUTOLOAD_FUNC_NAME,1);

    //把loader物件新增到autoload陣列裡面
    zend_hash_next_index_insert(Z_ARRVAL_P(autoload),&loader,sizeof(yaf_loader_t *),NULL);
    //把method新增到autoload陣列裡面
    zend_hash_next_index_insert(Z_ARRVAL_P(autoload),&method,sizeof(zval *),NULL);

    //設定function =  spl_autoload_register
    MAKE_STD_ZVAL(function);
    ZVAL_STRING(function,YAF_SPL_AUTOLOAD_REGISTER_NAME,0);
    
    //這裡註冊自動載入器跟php中呼叫spl_autoload_register形式幾乎差不多
    //spl_autoload_register(array(loader,autoload))
   
    do {
        zend_fcall_info fci = {
            sizeof(fci),EG(function_table),function,NULL,&ret,(zval ***)params,1
        };
        // 呼叫 spl_autoload_register 註冊
        if (zend_call_function(&fci,NULL TSRMLS_CC) == FAILURE) {
            if (ret) {
                zval_ptr_dtor(&ret);
            }
            efree(function);
            zval_ptr_dtor(&autoload);
            php_error_docref(NULL TSRMLS_CC,"Unable to register autoload function %s",YAF_AUTOLOAD_FUNC_NAME);
            return 0;
        }
        if (ret) {
            zval_ptr_dtor(&ret);
        }
        efree(function);
        zval_ptr_dtor(&autoload);
    } while (0);
    return 1;
}

複製程式碼

PHP_METHOD(yaf_loader,autoload) 自動載入器

PHP_METHOD(yaf_loader,autoload) {
    char *class_name,*origin_classname,*app_directory,*directory = NULL,*file_name = NULL;
#ifdef YAF_HAVE_NAMESPACE
    char *origin_lcname = NULL;
#endif
    uint separator_len,class_name_len,file_name_len = 0;
    
    //獲取類名
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s",&class_name,&class_name_len) == FAILURE) {
        return;
    }
    //分隔符長度
    separator_len = YAF_G(name_separator_len);
    //根目錄
    app_directory = YAF_G(directory);
    origin_classname = class_name;

    do {
        if (!class_name_len) {
            break;
        }
//名稱空間處理方式
#ifdef YAF_HAVE_NAMESPACE
        {
            int pos = 0;
            origin_lcname = estrndup(class_name,class_name_len);
            class_name    = origin_lcname;
            while (pos < class_name_len) {
                if (*(class_name + pos) == '\\') {
                    *(class_name + pos) = '_';
                }
                pos += 1;
            }
        }
#endif
#define YAF_LOADER_RESERVERD                "Yaf_"
        //不允許類名Yaf打頭
        if (strncmp(class_name,YAF_LOADER_RESERVERD,YAF_LOADER_LEN_RESERVERD) == 0) {
            php_error_docref(NULL TSRMLS_CC,"You should not use '%s' as class name prefix",YAF_LOADER_RESERVERD);
        }
#define YAF_LOADER_MODEL                    "Model"
        //是否屬於Model類
        if (yaf_loader_is_category(class_name,YAF_LOADER_MODEL,YAF_LOADER_LEN_MODEL TSRMLS_CC)) {
#define YAF_MODEL_DIRECTORY_NAME            "models"
            //獲取類檔案的路徑
            //app_directory/models
            spprintf(&directory,"%s/%s",app_directory,YAF_MODEL_DIRECTORY_NAME);
            //獲取檔名字的長度,減去分隔符和字尾Index_Model
            file_name_len = class_name_len - separator_len - YAF_LOADER_LEN_MODEL;
           //是否配置字尾式
            if (YAF_G(name_suffix)) {
                //獲取檔名字
                file_name = estrndup(class_name,file_name_len);
            } else {
                //獲取檔名字
                file_name = estrdup(class_name + YAF_LOADER_LEN_MODEL + separator_len);
            }

            break;
        }
#define YAF_LOADER_PLUGIN                   "Plugin"
        //是否屬於plugin類,流程跟上面一樣
        if (yaf_loader_is_category(class_name,YAF_LOADER_PLUGIN,YAF_LOADER_LEN_PLUGIN TSRMLS_CC)) {
            //獲取類檔案的路徑
            //app_directory/plugins
            spprintf(&directory,YAF_PLUGIN_DIRECTORY_NAME);
            file_name_len = class_name_len - separator_len - YAF_LOADER_LEN_PLUGIN;

            if (YAF_G(name_suffix)) {
                file_name = estrndup(class_name,file_name_len);
            } else {
                file_name = estrdup(class_name + YAF_LOADER_LEN_PLUGIN + separator_len);
            }

            break;
        }
#define YAF_LOADER_CONTROLLER               "Controller"
        //是否屬於Controller類,流程跟上面一樣
        if (yaf_loader_is_category(class_name,YAF_LOADER_CONTROLLER,YAF_LOADER_LEN_CONTROLLER TSRMLS_CC)) {
            //獲取類檔案的路徑
            //app_directory/controllers
            //可以看到這裡只能獲取Controllers目錄下的控制器
            //不能獲取models/model/controllers/這種形式
            spprintf(&directory,YAF_CONTROLLER_DIRECTORY_NAME);
            file_name_len = class_name_len - separator_len - YAF_LOADER_LEN_CONTROLLER;

            if (YAF_G(name_suffix)) {
                file_name = estrndup(class_name,file_name_len);
            } else {
                file_name = estrdup(class_name + YAF_LOADER_LEN_CONTROLLER + separator_len);
            }

            break;
        }


/* {{{ This only effects internally */
        if (YAF_G(st_compatible) && (strncmp(class_name,YAF_LOADER_DAO,YAF_LOADER_LEN_DAO) == 0
                    || strncmp(class_name,YAF_LOADER_SERVICE,YAF_LOADER_LEN_SERVICE) == 0)) {
            /* this is a model class */
            spprintf(&directory,YAF_MODEL_DIRECTORY_NAME);
        }
/* }}} */

        file_name_len = class_name_len;
        file_name     = class_name;

    } while(0);

    if (!app_directory && directory) {
        efree(directory);
#ifdef YAF_HAVE_NAMESPACE
        if (origin_lcname) {
            efree(origin_lcname);
        }
#endif
        if (file_name != class_name) {
            efree(file_name);
        }

        php_error_docref(NULL TSRMLS_CC,"Couldn't load a framework MVC class without an %s initializing",yaf_application_ce->name);
        RETURN_FALSE;
    }

    if (!YAF_G(use_spl_autoload)) {
        //載入這個類
        if (yaf_internal_autoload(file_name,file_name_len,&directory TSRMLS_CC)) {
            //把類名轉成小寫
            char *lc_classname = zend_str_tolower_dup(origin_classname,class_name_len);
            //是否存在這個類,如果存在則代表載入成功
            if (zend_hash_exists(EG(class_table),lc_classname,class_name_len + 1)) {
#ifdef YAF_HAVE_NAMESPACE
                if (origin_lcname) {
                    efree(origin_lcname);
                }
#endif
                if (directory) {
                    efree(directory);
                }

                if (file_name != class_name) {
                    efree(file_name);
                }

                efree(lc_classname);
                //返回成功
                RETURN_TRUE;
            } else {
                efree(lc_classname);
                php_error_docref(NULL TSRMLS_CC,"Could not find class %s in %s",directory);
            }
        }  else {
            php_error_docref(NULL TSRMLS_CC,"Failed opening script %s: %s",strerror(errno));
        }

#ifdef YAF_HAVE_NAMESPACE
        if (origin_lcname) {
            efree(origin_lcname);
        }
#endif
        if (directory) {
            efree(directory);
        }
        if (file_name != class_name) {
            efree(file_name);
        }
        RETURN_TRUE;
    } else {
        //跟上面流程差不多
        char *lower_case_name = zend_str_tolower_dup(origin_classname,class_name_len);
        if (yaf_internal_autoload(file_name,&directory TSRMLS_CC) &&
                zend_hash_exists(EG(class_table),lower_case_name,class_name_len + 1)) {
#ifdef YAF_HAVE_NAMESPACE
            if (origin_lcname) {
                efree(origin_lcname);
            }
#endif
            if (directory) {
                efree(directory);
            }
            if (file_name != class_name) {
                efree(file_name);
            }

            efree(lower_case_name);
            RETURN_TRUE;
        }
#ifdef YAF_HAVE_NAMESPACE
        if (origin_lcname) {
            efree(origin_lcname);
        }
#endif
        if (directory) {
            efree(directory);
        }
        if (file_name != class_name) {
            efree(file_name);
        }
        efree(lower_case_name);
        RETURN_FALSE;
    }
}

複製程式碼

yaf_internal_autoload() 載入類檔案

int yaf_internal_autoload(char *file_name,uint name_len,char **directory TSRMLS_DC) {
    zval *library_dir,*global_dir;
    char *q,*p,*seg;
    uint seg_len,directory_len,status;
    char *ext = YAF_G(ext);
    smart_str buf = {0};
    //判斷傳遞的路徑是否為空
    //如果為空則代表要載入的類檔案不屬於yaf框架規定的目錄
    //有可能是公共庫檔案目錄
    if (NULL == *directory) {
        char *library_path;
        uint  library_path_len;
        yaf_loader_t *loader;

        loader = yaf_loader_instance(NULL,NULL TSRMLS_CC);

        if (!loader) {
            /* since only call from userspace can cause loader is NULL,exception throw will works well */
            php_error_docref(NULL TSRMLS_CC,"%s need to be initialize first",yaf_loader_ce->name);
            return 0;
        } else {
            //獲取本地類庫地址
            library_dir = zend_read_property(yaf_loader_ce,loader,ZEND_STRL(YAF_LOADER_PROPERTY_NAME_LIBRARY),1 TSRMLS_CC);
            //獲取全域性類庫地址
            global_dir  = zend_read_property(yaf_loader_ce,ZEND_STRL(YAF_LOADER_PROPERTY_NAME_GLOBAL_LIB),1 TSRMLS_CC);
            //判斷類名字首是否已經註冊過,如果已經註冊過則在本地類庫去找
            //就是呼叫 Yaf_Loader::registerLocalNamespace() 註冊
            //如果不註冊的話全部去公共類庫下尋找
            if (yaf_loader_is_local_namespace(loader,file_name,name_len TSRMLS_CC)) {
                library_path = Z_STRVAL_P(library_dir);
                library_path_len = Z_STRLEN_P(library_dir);
            } else {
                library_path = Z_STRVAL_P(global_dir);
                library_path_len = Z_STRLEN_P(global_dir);
            }
        }

        if (NULL == library_path) {
            php_error_docref(NULL TSRMLS_CC,"%s requires %s(which set the library_directory) to be initialized first",yaf_loader_ce->name,yaf_application_ce->name);
            return 0;
        }

        smart_str_appendl(&buf,library_path,library_path_len);
    } else {
        smart_str_appendl(&buf,*directory,strlen(*directory));
        efree(*directory);
    }

    directory_len = buf.len;

    /* aussume all the path is not end in slash */
    smart_str_appendc(&buf,DEFAULT_SLASH);
    
    //如果這個檔名或者類名是這種形式的Service_Http_Post
    //下面這段程式碼就會把這個類名切分成路徑
    //directory/Service/Http/Post.php 這樣

    p = file_name;
    q = p;

    while (1) {
        while(++q && *q != '_' && *q != '\0');

        if (*q != '\0') {
            seg_len = q - p;
            seg     = estrndup(p,seg_len);
            smart_str_appendl(&buf,seg,seg_len);
            efree(seg);
            smart_str_appendc(&buf,DEFAULT_SLASH);
            p       = q + 1;
        } else {
            break;
        }
    }

    if (YAF_G(lowcase_path)) {
        /* all path of library is lowercase */
        zend_str_tolower(buf.c + directory_len,buf.len - directory_len);
    }

    smart_str_appendl(&buf,p,strlen(p));
    smart_str_appendc(&buf,'.');
    smart_str_appendl(&buf,ext,strlen(ext));

    smart_str_0(&buf);

    if (directory) {
        *(directory) = estrndup(buf.c,buf.len);
    }
    //這裡最後把類檔案匯入並呼叫核心介面進行編譯
    status = yaf_loader_import(buf.c,buf.len,0 TSRMLS_CC);
    smart_str_free(&buf);

    if (!status)
        return 0;

    return 1;
}

複製程式碼

結語

上面介紹的大致就是 Yaf 框架一個執行流程,並且把框架的主要程式碼都分析了一遍,可以以此作為引導,在閱讀分析原始碼的時候可以邊看原始碼邊對照 Yaf 框架官方檔案 或者在用Yaf框架搭建一個環境,執行下,在對照原始碼分析即可。