PHP-Yaf执行流程-源码分析

在application目录下有个Bootstrap.php文件,这个就是图中的第一个环节,如果存在Bootstrap()就会先执行该文件,该文件包含了一系列的初始化环节,并返回一个Yaf_Application对象,紧接着调用了它的run方法,run里面包含了图中所有环节,run首先是调用路由,路由的主要目的其实就是找到controllers文件,然后执行里面的init和action方法,或者找到所有actions的地址然后加载,在去执行对应的execute方法,如果设置了autoRender在返回的时候会执行render方法,就是view自动渲染,图中有六个双横线标出的环节,就是六个插件方法,用户可以自定义实现这几个方法,然后Yaf框架会在图中相应的步骤处调用对应的HOOK方法。
https://www.jianshu.com/p/130389235abc



Breakpoint 3, 0x0000000101078c44 in yaf_application_new () from /usr/local/lib/php/extensions/no-debug-non-zts-20170718/yaf.so
(gdb) c
Continuing.



Breakpoint 11, 0x0000000101079a14 in yaf_dispatcher_instance () from /usr/local/lib/php/extensions/no-debug-non-zts-20170718/yaf.so
(gdb) b yaf_router_route
Breakpoint 12 at 0x10108add4
(gdb) bt
#0 0x0000000101079a14 in yaf_dispatcher_instance () from /usr/local/lib/php/extensions/no-debug-non-zts-20170718/yaf.so
#1 0x0000000101077d69 in zim_yaf_application___construct () from /usr/local/lib/php/extensions/no-debug-non-zts-20170718/yaf.so
#2 0x000000010044b6d7 in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER (execute_data=0x101678120) at Zend/zend_vm_execute.h:911
#3 0x000000010040c8c8 in execute_ex (ex=) at Zend/zend_vm_execute.h:59767
#4 0x000000010040ca73 in zend_execute (op_array=0x10109ea50 <yaf_globals+16>, return_value=) at Zend/zend_vm_execute.h:63804
#5 0x00000001003c4dfb in zend_execute_scripts (type=, retval=0x0, file_count=) at Zend/zend.c:1498
#6 0x0000000100359685 in php_execute_script (primary_file=) at main/main.c:2599
#7 0x00000001004a0ec4 in do_cli (argc=, argv=) at sapi/cli/php_cli.c:1011
#8 0x000000010049fdd6 in main (argc=, argv=) at sapi/cli/php_cli.c:1403



https://www.iteye.com/blog/kenby-1979833
https://www.laruence.com/manual/
http://www.phpinternalsbook.com/
https://crispgm.com/page/php7-new-hashtable-implementation.html

yaf源码阅读之 – 框架基本生命周期 (yaf.c)
MINIT
php-fpm启动master时会启动php扩展,在yaf源码中,这里对应的是MINIT及之前的操作,相关源码位于yaf.c :



读取php.ini中的yaf配置
在PHP_INI_BEGIN()和PHP_INI_END()之间定义相关参数的默认值、作用域、回调函数等



在MINIT中定义YAF常量,如YAF_VERSION等



在MINIT中载入yaf框架各个组件,自此YAF框架就常驻内存,yaf的快的优势也源于此。



PHP_MINIT_FUNCTION(yaf)
{
REGISTER_INI_ENTRIES();
if (YAF_G(use_namespace)) {



	REGISTER_STRINGL_CONSTANT("YAF\\VERSION", PHP_YAF_VERSION, 	sizeof(PHP_YAF_VERSION) - 1, CONST_PERSISTENT | CONST_CS);
REGISTER_STRINGL_CONSTANT("YAF\\ENVIRON", YAF_G(environ_name), strlen(YAF_G(environ_name)), CONST_PERSISTENT | CONST_CS);

REGISTER_LONG_CONSTANT("YAF\\ERR\\STARTUP_FAILED", YAF_ERR_STARTUP_FAILED, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF\\ERR\\ROUTE_FAILED", YAF_ERR_ROUTE_FAILED, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF\\ERR\\DISPATCH_FAILED", YAF_ERR_DISPATCH_FAILED, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF\\ERR\\AUTOLOAD_FAILED", YAF_ERR_AUTOLOAD_FAILED, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF\\ERR\\NOTFOUND\\MODULE", YAF_ERR_NOTFOUND_MODULE, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF\\ERR\\NOTFOUND\\CONTROLLER",YAF_ERR_NOTFOUND_CONTROLLER, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF\\ERR\\NOTFOUND\\ACTION", YAF_ERR_NOTFOUND_ACTION, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF\\ERR\\NOTFOUND\\VIEW", YAF_ERR_NOTFOUND_VIEW, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF\\ERR\\CALL_FAILED", YAF_ERR_CALL_FAILED, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF\\ERR\\TYPE_ERROR", YAF_ERR_TYPE_ERROR, CONST_PERSISTENT | CONST_CS);

} else {
REGISTER_STRINGL_CONSTANT("YAF_VERSION", PHP_YAF_VERSION, sizeof(PHP_YAF_VERSION) - 1, CONST_PERSISTENT | CONST_CS);
REGISTER_STRINGL_CONSTANT("YAF_ENVIRON", YAF_G(environ_name),strlen(YAF_G(environ_name)), CONST_PERSISTENT | CONST_CS);

REGISTER_LONG_CONSTANT("YAF_ERR_STARTUP_FAILED", YAF_ERR_STARTUP_FAILED, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF_ERR_ROUTE_FAILED", YAF_ERR_ROUTE_FAILED, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF_ERR_DISPATCH_FAILED", YAF_ERR_DISPATCH_FAILED, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF_ERR_AUTOLOAD_FAILED", YAF_ERR_AUTOLOAD_FAILED, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF_ERR_NOTFOUND_MODULE", YAF_ERR_NOTFOUND_MODULE, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF_ERR_NOTFOUND_CONTROLLER", YAF_ERR_NOTFOUND_CONTROLLER, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF_ERR_NOTFOUND_ACTION", YAF_ERR_NOTFOUND_ACTION, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF_ERR_NOTFOUND_VIEW", YAF_ERR_NOTFOUND_VIEW, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF_ERR_CALL_FAILED", YAF_ERR_CALL_FAILED, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("YAF_ERR_TYPE_ERROR", YAF_ERR_TYPE_ERROR, CONST_PERSISTENT | CONST_CS);
}

/* startup components */
YAF_STARTUP(application);
YAF_STARTUP(bootstrap);
YAF_STARTUP(dispatcher);
YAF_STARTUP(loader);
YAF_STARTUP(request);
YAF_STARTUP(response);
YAF_STARTUP(controller);
YAF_STARTUP(action);
YAF_STARTUP(config);
YAF_STARTUP(view);
YAF_STARTUP(router);
YAF_STARTUP(plugin);
YAF_STARTUP(registry);
YAF_STARTUP(session);
YAF_STARTUP(exception);

return SUCCESS; } RINIT 请求初始化只是对yaf用户应用级别的配置做了初始化


PHP_RINIT_FUNCTION(yaf)
{
YAF_G(throw_exception) = 1;
YAF_G(ext) = zend_string_init(YAF_DEFAULT_EXT, sizeof(YAF_DEFAULT_EXT) - 1, 0);
YAF_G(view_ext) = zend_string_init(YAF_DEFAULT_VIEW_EXT, sizeof(YAF_DEFAULT_VIEW_EXT) - 1, 0);
YAF_G(default_module) = zend_string_init(
YAF_ROUTER_DEFAULT_MODULE, sizeof(YAF_ROUTER_DEFAULT_MODULE) - 1, 0);
YAF_G(default_controller) = zend_string_init(
YAF_ROUTER_DEFAULT_CONTROLLER, sizeof(YAF_ROUTER_DEFAULT_CONTROLLER) - 1, 0);
YAF_G(default_action) = zend_string_init(
YAF_ROUTER_DEFAULT_ACTION, sizeof(YAF_ROUTER_DEFAULT_ACTION) - 1, 0);
return SUCCESS;
}
RSHUTDOWN
销毁用户应用级别的配置



PHP_RSHUTDOWN_FUNCTION(yaf)
{
YAF_G(running) = 0;
YAF_G(in_exception) = 0;
YAF_G(catch_exception) = 0;



if (YAF_G(directory)) {
zend_string_release(YAF_G(directory));
YAF_G(directory) = NULL;
}
if (YAF_G(local_library)) {
zend_string_release(YAF_G(local_library));
YAF_G(local_library) = NULL;
}
if (YAF_G(local_namespaces)) {
zend_string_release(YAF_G(local_namespaces));
YAF_G(local_namespaces) = NULL;
}
if (YAF_G(bootstrap)) {
zend_string_release(YAF_G(bootstrap));
YAF_G(bootstrap) = NULL;
}
if (Z_TYPE(YAF_G(modules)) == IS_ARRAY) {
zval_ptr_dtor(&YAF_G(modules));
ZVAL_UNDEF(&YAF_G(modules));
}
if (YAF_G(base_uri)) {
zend_string_release(YAF_G(base_uri));
YAF_G(base_uri) = NULL;
}
if (YAF_G(view_directory)) {
zend_string_release(YAF_G(view_directory));
YAF_G(view_directory) = NULL;
}
if (YAF_G(view_ext)) {
zend_string_release(YAF_G(view_ext));
}
if (YAF_G(default_module)) {
zend_string_release(YAF_G(default_module));
}
if (YAF_G(default_controller)) {
zend_string_release(YAF_G(default_controller));
}
if (YAF_G(default_action)) {
zend_string_release(YAF_G(default_action));
}
if (YAF_G(ext)) {
zend_string_release(YAF_G(ext));
}
YAF_G(default_route) = NULL;

return SUCCESS; } MSHUTDOWN 销毁yaf配置


PHP_MSHUTDOWN_FUNCTION(yaf)
{
UNREGISTER_INI_ENTRIES();



if (YAF_G(configs)) {
zend_hash_destroy(YAF_G(configs));
pefree(YAF_G(configs), 1);
}

return SUCCESS; }


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, 0, "%s%c%s", app_dir, DEFAULT_SLASH, YAF_CONTROLLER_DIRECTORY_NAME);
} else {
// directory = app_dir/modules/mymodule/controllers
directory_len = spprintf(&directory, 0, "%s%c%s%c%s%c%s", app_dir, DEFAULT_SLASH,
YAF_MODULE_DIRECTORY_NAME, DEFAULT_SLASH, module, DEFAULT_SLASH, 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, 0, "%s%s%s", controller, YAF_G(name_separator), "Controller");
} else {
class_len = spprintf(&class, 0, "%s%s%s", "Controller", YAF_G(name_separator), 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), class_lowercase, class_len + 1, (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_handle() 调用了yaf_dispatcher_get_controller()


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(module), Z_STRVAL_P(controller), Z_STRLEN_P(controller), is_def_module TSRMLS_CC);



https://note.youdao.com/ynoteshare1/index.html?id=506a5d51332ac354fdbaa5fb902b2e1f&type=note



https://blog.wislay.com/articles/543
本篇主要简单记录了:



yaf.c
yaf_application.c
yaf_bootstrap.c
yaf_controller.c
yaf_dispatcher.c
yaf_exception.c
yaf_loader.c
yaf_plugin.c
yaf_registry.c
源码阅读过程中的一些问题和理解。



config.m4
扩展源码阅读,从 config.m4 文件开始。



对于这个文件,最值得注意的应该是 PHP_NEW_EXTENSION 这一个函数声明了。声明了这一个扩展的名称是 yaf,需要编译 yaf.c 等多个文件,以及是否 build 到 PHP 的二进制文件中。



yaf.c
yaf.c 这一个文件中,主要做了如下的工作:



定义各个生命周期的回调函数(MINIT/RINIT/RSHUTDOWN/MSHUTDOWN)
定义 ini 中可配置的项目
声明依赖
加载所需模块
定义各个生命周期的回调方法
PHP 扩展的生命周期可以简单的概括为如下几个步骤:



MINIT
RINIT
RSHUTDOWN
MSHUTDOWN
其中 RINIT/RSHUTDOWN 在每次请求 PHP 代码执行过程中都会执行一次。



资源和全局的一些初始化工作可以在 MINIT 回调函数中进行。如 yaf 的 MINIT 方法中,就完成了声明 ini 可配置项目(REGISTER_INI_ENTRIES()),常量定义,以及模记载。



与 MINIT 相对的,MSHUTDOWN 阶段的回调函数则做了资源释放额操作,如释放了读取配置所需要使用的内存空间。



定义 ini 中可配置的项目
定义 ini 文件中的可配置项目代码段自 PHP_INI_BEGIN() 开始,到 PHP_INI_END() 结束,通过名如 STD_PHP_INI_* 的宏进行设定。



PHP_INI_BEGIN()
STD_PHP_INI_ENTRY(“yaf.library”, “”, PHP_INI_ALL, OnUpdateString, global_library, zend_yaf_globals, yaf_globals)

STD_PHP_INI_BOOLEAN(“yaf.use_namespace”, “0”, PHP_INI_SYSTEM, OnUpdateBool, use_namespace, zend_yaf_globals, yaf_globals)
#endif
PHP_INI_END();



1
2
3
4
5
6
7
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY(“yaf.library”, “”, PHP_INI_ALL, OnUpdateString, global_library, zend_yaf_globals, yaf_globals)

STD_PHP_INI_BOOLEAN(“yaf.use_namespace”, “0”, PHP_INI_SYSTEM, OnUpdateBool, use_namespace, zend_yaf_globals, yaf_globals)
#endif
PHP_INI_END();



这里需要注意的是,宏的第三个参数有 PHP_INI_ALL 和 PHP_INI_SYSTEM 两种值,这两个值决定了是否可以在运行时修改这类参数。设定为 PHP_INI_SYSTEM 的配置项是不允许在运行时改变的。



所以,想要启用 yaf 的命名空间模式,就必须在 ini 中进行开启。



yaf_application.c
这一文件主要定义了 Yaf_Application 这一个 class。



定义的内容包括:



类名与命名空间名称
类属性与访问权限控制(Yaf_Application 这一个 class 被定义为 final class,即不能再被继承)
类的方法与访问权限控制
除了上述内容,还实现了配置项解析和初始化功能。下面会对一些重要的方法进行简单的描述。



__construct()
构造方法中主要完成了如下的一些工作:



解析构造方法中的参数
初始化 request/dispatcher/loader 等对象
读取解析配置文件
从手册上可以得知构造方法的原型为:



public void Yaf_Application::__construct(mixed $config,
string $section = ap.environ);



1
2
3
public void Yaf_Application::__construct(mixed $config,
string $section = ap.environ);



根据传递的字符串作为本应用 ini 配置文件的文件名,进行解析。



第二个参数 section 存在的情况下,会只读取 section: 开头的配置项目。



如,设定 section 为 product 之后,读取到的数据库端口配置项应为 3306。



[common]
application.directory = APPLICATION_PATH “/application”
application.dispatcher.catchException = TRUE



[product:common]
database.driver = “mysql”
database.host = “127.0.0.1”
database.port = “3306”
database.database = “learn”
database.username = “learn”
database.password = “123456”
database.charset = “utf8”
database.collation = “utf8_general_ci”
database.prefix = “”



[dev:common]
database.driver = “mysql”
database.host = “127.0.0.1”
database.port = “13306”
database.database = “learn”
database.username = “learn”
database.password = “123456”
database.charset = “utf8”
database.collation = “utf8_general_ci”
database.prefix = “”



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[common]
application.directory = APPLICATION_PATH “/application”
application.dispatcher.catchException = TRUE



[product:common]
database.driver = “mysql”
database.host = “127.0.0.1”
database.port = “3306”
database.database = “learn”
database.username = “learn”
database.password = “123456”
database.charset = “utf8”
database.collation = “utf8_general_ci”
database.prefix = “”



[dev:common]
database.driver = “mysql”
database.host = “127.0.0.1”
database.port = “13306”
database.database = “learn”
database.username = “learn”
database.password = “123456”
database.charset = “utf8”
database.collation = “utf8_general_ci”
database.prefix = “”



根据方法原型中传入的配置文件路径,会解析出如 bootstrap 类所在文件路径之类的配置,并赋值到全局变量中,供其他功能使用。



在读取配置文件的过程中,还会通过 yaf_loader_register() 函数注册默认的自动加载方法: Yaf_Loader::autoload()。



run()
run() 方法完成的工作比较简单,判断当前 app 对象是否已经在运行中,如果在运行中则产生错误,否则执行 dispatch 过程,获得 response 对象。



bootstrap()
bootstrap() 方法是一个比较重要的方法,这个方法可以对 yaf 进行一些全局的初始化操作。



首先会在类名表(EG(class_table))中查找名为 YAF_DEFAULT_BOOTSTRAP_LOWER 即名为 bootstrap 的类。



如果不存在这样的一个 class,则通过读取名为 bootstrap 的全局变量(YAF_G(bootstrap)),来确定具体的需要执行的类所在的文件。如果全局变量也没有配置,则会在当前目录中查找是否存在 Bootstrap.php 的文件。bootstrap 这一个全局变量,对应的是传入的 ini 文件中的 application.bootstrap 配置项的值。



在确定需要执行的 bootstrap 的文件路径之后,通过 yaf_loader_import() 函数加载文件。并会再次尝试在类名表(EG(class_table))中查找名为 YAF_DEFAULT_BOOTSTRAP_LOWER 的类,最后判断这个类是否继承了 Yaf_Bootstrap_Abstract,任何一项不满足,都会触发错误。



摘录一下上述这段源码:



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;
} else if (zend_hash_find(EG(class_table), YAF_DEFAULT_BOOTSTRAP_LOWER, YAF_DEFAULT_BOOTSTRAP_LEN, (void *) &ce) != SUCCESS) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, “Couldn’t find class %s in %s”, YAF_DEFAULT_BOOTSTRAP, bootstrap_path);
retval = 0;
} else if (!instanceof_function(
ce, yaf_bootstrap_ce TSRMLS_CC)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, “Expect a %s instance, %s give”, yaf_bootstrap_ce->name, (*ce)->name);
retval = 0;
}



1
2
3
4
5
6
7
8
9
10
11
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;
} else if (zend_hash_find(EG(class_table), YAF_DEFAULT_BOOTSTRAP_LOWER, YAF_DEFAULT_BOOTSTRAP_LEN, (void *) &ce) != SUCCESS) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, “Couldn’t find class %s in %s”, YAF_DEFAULT_BOOTSTRAP, bootstrap_path);
retval = 0;
} else if (!instanceof_function(
ce, yaf_bootstrap_ce TSRMLS_CC)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, “Expect a %s instance, %s give”, yaf_bootstrap_ce->name, (*ce)->name);
retval = 0;
}



yaf_loader_import() 成功加载之后返回值是1,zend_hash_find() 执行成功之后返回值是 SUCCESS 即0,instanceof_function() 判断为真时返回值是1,所以,上述一段代码,在一切正常的逻辑下,三个 if/else 判断中的语句都会执行。



所以,上述步骤说明了两点:



bootstrap 文件的绝对路径是可以配置的
bootstrap 类名必须是 Bootstrap(因为只会在类名表中查找这一个值)
在类加载完成后,会逐个调用 _init 开头的方法,完成初始化操作,所有方法会接收 dispatcher 作为参数。



yaf_bootstrap.c
这一文件主要是声明了 Yaf_Bootstrap_Abstract 这一个抽象类。



yaf_controller.c
这一文件声明了 Yaf_Controller_Abstract 这一个抽象类。同时也定义了视图层的 render 与 display 操作,二者区别在于 render 返回渲染好的视图字符串(准确来说是一个 zval)。



yaf_dispatcher.c
这是 yaf 执行过程中的关键部分之一。



一个请求到来,Yaf_Application 对象在执行 run() 方法时,最后一步就是通过已经设置好的 dispatcher 开始对请求进行处理,即调用 yaf_dispatcher_dispatch() 函数。



yaf_dispatcher_dispatch()
路由
yaf_dispatcher_dispatch() 函数在处理过程中,会先判断当前请求是否已经被路由过,如果没有被路由过,则通过 yaf_dispatcher_route() 函数对当前请求执行路由操作。



在执行路由之前,yaf 的钩子机制会通过 YAF_PLUGIN_HANDLE 这个宏逐个调用已注册插件中 routerstartup 回调方法,调用顺序为注册插件的顺序。



Yaf_Dispatcher 中包含一个成员变量 _router (即 YAF_DISPATCHER_PROPERTY_NAME_ROUTER),此处记录了当前 app 已注册的各个路由规则。在初始化阶段(调用 yaf_router_instance() 函数),会注册默认路由,如果不设定,则使用的是 static 路由。



同样的,一个 app 的 Dispatcher 对象的默认 Controller/Module/Action 等等参数,以及需要执行的插件,也都在这一个阶段完成了默认值的设定。



如果有多个路由规则,这里要注意,后加入的路由规则会先被执行,此处在手册中也可以获知,从代码上来看,原因是 yaf_router_route() 函数在遍历 HashTable 时从 HashTable 的末端开始进行遍历:




ht = Z_ARRVAL_P(routers);
for(zend_hash_internal_pointer_end(ht);
zend_hash_has_more_elements(ht) == SUCCESS;
zend_hash_move_backwards(ht)) {



1
2
3
4
5
6
7

ht = Z_ARRVAL_P(routers);
for(zend_hash_internal_pointer_end(ht);
zend_hash_has_more_elements(ht) == SUCCESS;
zend_hash_move_backwards(ht)) {



一旦路由规则命中,则结束路由过程。



在执行路由之后,yaf 的钩子机制会通过 YAF_PLUGIN_HANDLE 这个宏逐个调用已注册插件中 routershutdown 回调方法,调用顺序为注册插件的顺序。



分发
分发开始之前,yaf 的钩子机制会通过 YAF_PLUGIN_HANDLE 这个宏逐个调用已注册插件中 dispatchloopstartup 回调方法,调用顺序为注册插件的顺序。



之后会执行视图的初始化操作。



请求在分发过程中有最大 forward 次数(将请求交给指定的 module / controller / action 处理),即配置文件中 yaf.forward_limit 这一个配置项,这个配置可以避免让用户请求陷入无限循环处理的问题之中(如用户权限系统出现bug,无限转入登录逻辑)。



每一次分发过程中,yaf 的钩子机制会先通过 YAF_PLUGIN_HANDLE 这个宏逐个调用已注册插件中 predispatch 回调方法,之后通过 yaf_dispatcher_handle() 函数实际处理请求,请求完成之后在通过 YAF_PLUGIN_HANDLE 这个宏逐个调用已注册插件中 postdispatch 回调方法。



在请求执行完成之后,向用户发送对应的请求结果。



yaf_dispatcher_handle()
yaf_dispatcher_handle() 这一方法完成的工作是从 request 对象中得知当前的 module 与 controller,之后找到对应的文件,实例化对应的 Controller,为对应的 Controller 设定好模板目录等这类基础属性。



之后会从 request 对象中获取 action,即实际需要执行的方法。



执行完对应的action之后,如果执行结果的返回值不为真值(非0),则不会执行渲染页面以及输出等工作。



如果 Dispatcher 中设置了名为 $_auto_render 且值为真的成员变量(yaf_dispatcher.h 中的 #define YAF_DISPATCHER_PROPERTY_NAME_RENDER “_auto_render”),当前 Controller 可能会触发自动输出。



这里说可能,是因为 Controller 中的成员变量也会影响到这一行为。



如果 Controller 中设置了名为 $yafAutoRender 且值为真的成员变量(yaf_controller.h 中的 #define YAF_CONTROLLER_PROPERTY_NAME_RENDER “yafAutoRender”),当前 Controller 会触发自动输出。只有 Controller 中没有设定这一个成员变量,Dispatcher 中的配置才会产生影响。



实际上,Yaf_Dispatcher 中的 enableView()/disableView() 方法所做的就是修改这一成员变量的值。



yaf_exception.c
主要定义了各种类型的异常类。



yaf_loader.c
Yaf 的又一核心组成部分,代码与类自动加载器。



import()
import() 方法主要完成的工作是加载对应的 PHP 文件到当前执行环境。实际上仍然调用的是 yaf_loader_import() 函数进行加载工作。



autoload()
当代码遇到当前文件中未定义的类时,需要自动加载器完成对应代码的加载工作。



从这个方法的逻辑可以看到 yaf 自动加载的规律。



在未做特殊配置的情况下,在默认模块下进行开发,简单来说可以概括成如下几个:



代码的起始查找目录都在于在ini中定义的application.directory(此处值可以使用PHP代码中的预定义常量)
_表示目录分隔符,class Foo_BarBar_Var等同于目录Foo/BarBar/Var.php
Controller为结尾的类会在controllers目录下进行查找
Model为结尾的类会在models目录下进行查找
Plugin为结尾的类会在plugins目录下进行查找
其他类会在library目录下进行查找
use_spl_autoload 配置项作用
手册里面提及:



在use_spl_autoload关闭的情况下, Yaf Autoloader在一次找不到的情况下, 会立即返回, 而剥夺其后的自动加载器的执行机会.



从代码执行逻辑上来看,确实如此,spl_autoload_register() 这一函数在注册的回调方法返回 TRUE 时,不会调用已注册函数列表中的下一个加载函数。



Yaf 在设置这一个值为空或者关闭时(0),做法是无论何种情况都返回真值:



if (!YAF_G(use_spl_autoload)) {
/** directory might be NULL since we passed a NULL */
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)) {

RETURN_TRUE; // 注意
} else {
efree(lc_classname);
php_error_docref(NULL TSRMLS_CC, E_STRICT, “Could not find class %s in %s”, class_name, directory);
}
} else {
php_error_docref(NULL TSRMLS_CC, E_WARNING, “Failed opening script %s: %s”, directory, strerror(errno));
}




RETURN_TRUE; // 注意
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (!YAF_G(use_spl_autoload)) {
/** directory might be NULL since we passed a NULL */
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)) {

RETURN_TRUE; // 注意
} else {
efree(lc_classname);
php_error_docref(NULL TSRMLS_CC, E_STRICT, “Could not find class %s in %s”, class_name, directory);
}
} else {
php_error_docref(NULL TSRMLS_CC, E_WARNING, “Failed opening script %s: %s”, directory, strerror(errno));
}




RETURN_TRUE; // 注意
}



但是在集成 Eloquent 的尝试里,在配置文件中并没有配置这一个项目为1(即开启),按照扩展源码的逻辑,Composer 生成的加载器应该不起作用,这个和实际情况不符,因为 Eloquent 在样例程序中表现正常。



究其原因,其实很简单,即 Composer 生成的自动加载器在注册时要求注册到了加载函数队列的首位。



先来看 spl_autoload_register() 方法的原型:



bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )



1
2
bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )



让人值得注意的是 $prepend 参数:



prepend
如果是 true,spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。



1
2
3
prepend
如果是 true,spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。



进入到 vendor 目录,翻看 autoload_real.php 源码,可以看到:



class ComposerAutoloaderInit4604f3b23b635a9f5adc52f8616258a1
{
private static $loader;



public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}

public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}

...

$loader->register(true); // 注意,这里为 true

...

return $loader;
} }


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ComposerAutoloaderInit4604f3b23b635a9f5adc52f8616258a1
{
private static $loader;



public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}

public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}

...

$loader->register(true); // 注意,这里为 true

...

return $loader;
} }


这个 register 方法的定义为:



/**



  • Registers this instance as an autoloader.
    *

  • @param bool $prepend Whether to prepend the autoloader or not
    */
    public function register($prepend = false)
    {
    spl_autoload_register(array($this, ‘loadClass’), true, $prepend);
    }



1
2
3
4
5
6
7
8
9
10
/**



  • Registers this instance as an autoloader.
    *

  • @param bool $prepend Whether to prepend the autoloader or not
    */
    public function register($prepend = false)
    {
    spl_autoload_register(array($this, ‘loadClass’), true, $prepend);
    }



至此,可以看到,我们通过 Composer 生成的自动加载方法,实际上会优先于 yaf 自身的自动加载方法,由于 yaf 的自动加载方法也是通过 spl_autoload_register() 方法注册的,处于同一个加载函数队列,在 Composer 声明优先的情况下,加载函数执行顺序就会发生变化。



yaf_plugin.c
插件抽象类 Yaf_Plugin_Abstract 的定义。在这里,可以看到一个插件可以实现的 hook 方法:



zend_function_entry yaf_plugin_methods[] = {
PHP_ME(yaf_plugin, routerStartup, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, routerShutdown, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, dispatchLoopStartup, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, dispatchLoopShutdown, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, preDispatch, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, postDispatch, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, preResponse, plugin_arg, ZEND_ACC_PUBLIC)
{NULL, NULL, NULL}
};



1
2
3
4
5
6
7
8
9
10
11
zend_function_entry yaf_plugin_methods[] = {
PHP_ME(yaf_plugin, routerStartup, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, routerShutdown, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, dispatchLoopStartup, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, dispatchLoopShutdown, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, preDispatch, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, postDispatch, plugin_arg, ZEND_ACC_PUBLIC)
PHP_ME(yaf_plugin, preResponse, plugin_arg, ZEND_ACC_PUBLIC)
{NULL, NULL, NULL}
};



他们都只接受两个参数:request 与 response。



ZEND_BEGIN_ARG_INFO_EX(plugin_arg, 0, 0, 2)
ZEND_ARG_OBJ_INFO(0, request, Yaf_Request_Abstract, 0)
ZEND_ARG_OBJ_INFO(0, response, Yaf_Response_Abstract, 0)
ZEND_END_ARG_INFO()



1
2
3
4
5
ZEND_BEGIN_ARG_INFO_EX(plugin_arg, 0, 0, 2)
ZEND_ARG_OBJ_INFO(0, request, Yaf_Request_Abstract, 0)
ZEND_ARG_OBJ_INFO(0, response, Yaf_Response_Abstract, 0)
ZEND_END_ARG_INFO()



yaf_registry.c
Yaf 全局存储类 Yaf_Registry 定义。用于在整个 app 声明周期内存储共有数据。所有数据都会存储到一个 zval 之中。



https://blog.wislay.com/articles/549



Yaf 版本为 2.3.0。



本篇主要简单记录了:



yaf_request.c
yaf_response.c
yaf_router.c
yaf_session.c
源码阅读过程中的一些问题和理解。



yaf_request.c
定义了 Yaf_Request_Abstract 这一抽象类。同时以及声明了这些类型的 getter / setter 方法。



一个 yaf 的 request 包含了需要调用的 Controller / Action 等等信息。



一个应用场景是在开发过程中,可以通过插件在分发前根据特定情况改动 request 的信息,使得可以更改请求触发的操作对象。



yaf_response.c
定义了 Yaf_Response_Abstract 这一抽象类。



Response 对象中关键的操作是返回内容的操作以及实际返回内容的方法。



返回内容可以在多次的 foward 等过程中,在当前返回数据之前或者之后进行追加操作,也可以直接替换最终返回的数据。这些操作都通过 yaf_response_alter_body() 函数实现,这一函数可以支持:



YAF_RESPONSE_PREPEND 添加到头部
YAF_RESPONSE_APPEND 追加到尾部
YAF_RESPONSE_REPLACE 修改数据
相对应的就是 response 对象中的 prependBody() / appendBody() / setBody() 方法。



对于 HTTP 协议上诸如 Header 等内容的操作,也在这个文件中进行了定义。



yaf_router.c
这是 yaf 框架最为重要的组成部分之一。



在此文件中,定义了 Yaf_Router 这一 class。同时也定义了 static / simple / supervar / rewrite / regex / map 这几个路由规则。



路由过程与添加路由规则的顺序相反,在上一篇文章中有所提及。



无论是内置路由规则,还是新增的路由规则,都需要实现 Yaf_Route_Interface 这一个接口,实现 route() 方法,接受 request 对象,认定为匹配之后,修改当前请求对象的 module / controller / action,并返回真值。



如果没有设定,则使用 static 路由规则。



实际使用过程中,可以通过当前 app 的 dispatcher 中的 router 添加一个实例化的路由规则,实现自己路由的目的。



assemble()
assemble() 方法是一个根据自身路由规则拼装出合理 url 的工具,每一个路由类型都需要结合自身的规则,来实现这个方法。



Yaf 路由时需要知道 module / controller / action,所以在调用 assemble() 时,自然也要通过数组的方式,传递这些参数,即源码中的:



YAF_ROUTE_ASSEMBLE_MOUDLE_FORMAT :m
YAF_ROUTE_ASSEMBLE_CONTROLLER_FORMAT :c
YAF_ROUTE_ASSEMBLE_ACTION_FORMAT :a
这是一个了解各个路由的手段。



关于具体路由的使用,另起篇幅。



yaf_session.c
定义了 Yaf_Session 这一个类。



可以通过 getInstance() 方法获得单例。



Yaf_Session 类只是对 $_SESSION 进行了封装,实际上操作的还是 $_SESSION 变量,在初始化过程中将 $_SESSION 变量和成员属性 sess 进行了关联



https://www.kancloud.cn/wuzhc/note/798302


Category php