在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=
#4 0x000000010040ca73 in zend_execute (op_array=0x10109ea50 <yaf_globals+16>, return_value=
#5 0x00000001003c4dfb in zend_execute_scripts (type=
#6 0x0000000100359685 in php_execute_script (primary_file=
#7 0x00000001004a0ec4 in do_cli (argc=
#8 0x000000010049fdd6 in main (argc=
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 方法的定义为:
/**
1
2
3
4
5
6
7
8
9
10
/**
至此,可以看到,我们通过 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