zoukankan      html  css  js  c++  java
  • Yaf源码阅读之框架的启动(一)

    (一)Yaf是什么

    Yaf,全称 Yet Another Framework,是一个C语言编写的、基于PHP扩展开发的PHP框架,

    相比于一般的PHP框架,它更快,快到被誉为最快的PHP开发框架。

    它提供了Bootstrap、路由、分发、视图、插件功能。

    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,Msgpack

    等项目的维护者...此处略去1000字。总之是一位牛得掉渣的人,Yaf在百度和新浪微博得到广泛使用。

    在百度,绝大部分产品都是PHP开发的,其中绝大部分PHP程序都使用了Yaf框架;在新浪微博,Yaf也得到了广泛使用。这些公司的产品都是高并发、超大流量的,Yaf经受住了考验。

    (二)Yaf的优点

    • 用C语言开发的PHP框架,相比原生的PHP, 几乎不会带来额外的性能开销。
    • 所有的框架类,不需要编译,在PHP启动的时候加载, 并常驻内存。
    • 更短的内存周转周期,提高内存利用率,降低内存占用率。
    • 灵巧的自动加载。支持全局和局部两种加载规则,方便类库共享。
    • 高度灵活可扩展的框架,支持自定义视图引擎,支持插件,支持自定义路由等等。
    • 内建多种路由,可以兼容目前常见的各种路由协议。
    • 强大而又高度灵活的配置文件支持。并支持缓存配置文件,避免复杂的配置结构带来的性能损失。
    • 在框架本身,对危险的操作习惯做了禁止。
    • 更快的执行速度, 更少的内存占用。

    以上内容引用鸟哥的官方介绍,当然,Yaf不是一个Full-Stack的web框架,它没有对数据库操作

    的封装,更不用说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将会变成YafApplication
    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... ");  
    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通过这种方式完成所有框架类的加载。

  • 相关阅读:
    结构与联合
    信号与槽知识点
    函数
    元对象编译器moc
    堆与栈
    More Effective C++ (虚拟构造函数)
    重载 覆盖 隐藏
    继承专题
    PowerDesigner 的常用方法(转)
    搜狗输入法导入Google词库bug
  • 原文地址:https://www.cnblogs.com/mashuaimama/p/3525540.html
Copyright © 2011-2022 走看看