用Php扩展实现的简单框架 - 3
2008-03-24 15:03
866 查看
kiss.c:
#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" #include "ext/standard/php_string.h" #include "ext/standard/url.h" #include "SAPI.h" #include "php_kiss.h" #include "kiss_const.c" #include "kiss_inner.c" // #define KISS_DEPS /** HAVE_SIMPLEXML */ #ifdef KISS_DEPS /** HAVE_SIMPLEXML */ static zend_module_dep kiss_deps[] = { // ZEND_MOD_REQUIRED("libxml") // ZEND_MOD_REQUIRED("simplexml") {NULL, NULL, NULL} }; #endif /* True global resources - no need for thread safety here */ // static HashTable hosts_rw; ZEND_DECLARE_MODULE_GLOBALS(kiss) zend_function_entry action_mtds[] = { PHP_ME(KissAction, __construct, NULL, ZEND_ACC_PUBLIC) // PHP_ME(KissAction, init, NULL, ZEND_ACC_PUBLIC) // PHP_ABSTRACT_ME(KissAction, init, NULL) {NULL, NULL, NULL} }; zend_function_entry listener_mtds[] = { PHP_ME(KissListener, __construct, NULL, ZEND_ACC_PUBLIC) PHP_ME(KissListener, preDispatch, NULL, ZEND_ACC_PUBLIC) PHP_ME(KissListener, postDispatch, NULL, ZEND_ACC_PUBLIC) {NULL, NULL, NULL} }; zend_function_entry kiss_functions[] = { PHP_FE(kiss_cli_test, NULL) PHP_FE(kiss_status, NULL) PHP_FE(kiss_front_start, NULL) PHP_FE(kiss_eval, NULL) PHP_FE(kiss_forward, NULL) PHP_FE(kiss_redirect, NULL) PHP_FE(kiss_request_get, NULL) PHP_FE(kiss_request_query, NULL) PHP_FE(kiss_request_post, NULL) PHP_FE(kiss_request_params, NULL) PHP_FE(kiss_set_rule, NULL) PHP_FE(kiss_clear_rules, NULL) PHP_FE(kiss_set_listener, NULL) {NULL, NULL, NULL} }; zend_module_entry kiss_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 #ifdef KISS_DEPS /** HAVE_SIMPLEXML */ STANDARD_MODULE_HEADER_EX, NULL, kiss_deps, #else STANDARD_MODULE_HEADER, #endif #endif "kiss", kiss_functions, PHP_MINIT(kiss), PHP_MSHUTDOWN(kiss), PHP_RINIT(kiss), PHP_RSHUTDOWN(kiss), PHP_MINFO(kiss), #if ZEND_MODULE_API_NO >= 20010901 KISS_VERSION, #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_KISS ZEND_GET_MODULE(kiss) #endif PHP_INI_BEGIN() STD_PHP_INI_ENTRY(CFG_FILE_ENTRY, DEFAULT_CFG_FILE, PHP_INI_SYSTEM, / OnUpdateString, cfg_file, zend_kiss_globals, kiss_globals) PHP_INI_END() /** co: zval **, var: global-var, type of zval* */ #define clear_var_pzval(co, var) { / (co) = &KISS_G(var); if(*(co)) zval_ptr_dtor((co)); *(co) = NULL; / } static void php_kiss_init_globals(zend_kiss_globals *kiss_globals) { zend_hash_init(&kiss_globals->hosts_rw, 0 , NULL, (void (*)(void *)) kiss_rule_free, 1); zend_hash_init(&kiss_globals->hosts_ls, 0 , NULL, NULL/*(void (*)(void *)) kiss_ls_free*/, 1); } static void php_kiss_destroy_globals(zend_kiss_globals *kiss_globals) { zend_hash_destroy(&kiss_globals->hosts_rw); zend_hash_destroy(&kiss_globals->hosts_ls); } PHP_MINIT_FUNCTION(kiss) { zend_class_entry ce; zval * pzv; ZEND_INIT_MODULE_GLOBALS(kiss, php_kiss_init_globals, NULL); /* 第三个参数传入NULL将导致在非多线程的平台产生段错误, * 传入php_kiss_destroy_globals导致段错误。 */ REGISTER_INI_ENTRIES(); KISS_EXPORT_CONST MAKE_STD_ZVAL(pzv); if(!zend_get_constant(DIR_SEP, DIR_SEP_LEN, pzv TSRMLS_CC)) { KISS_G(dir_sep) = Z_STRVAL_P(pzv)[0]; zval_ptr_dtor(&pzv); } else KISS_G(dir_sep) = DEFAULT_DIR_SEP; INIT_CLASS_ENTRY(ce, "KissAction", action_mtds); KISS_G(action_ce) = zend_register_internal_class(&ce TSRMLS_CC); //KISS_G(action_ce)->ce_flags != ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; INIT_CLASS_ENTRY(ce, "KissListener", listener_mtds); KISS_G(listener_ce) = zend_register_internal_class(&ce TSRMLS_CC); //KISS_G(listener_ce)->ce_flags != ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; KISS_G(cfg_inited) = 0; #ifndef PHP_WIN32 kiss_init_cfg(KISS_G(cfg_file) TSRMLS_CC); #endif return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(kiss) { UNREGISTER_INI_ENTRIES(); #ifdef ZTS zend_hash_destroy(&KISS_G(hosts_rw)); zend_hash_destroy(&KISS_G(hosts_ls)); #else php_kiss_destroy_globals(&kiss_globals TSRMLS_CC); #endif return SUCCESS; } PHP_RINIT_FUNCTION(kiss) { zval **co; KISS_G(status) = kiss_status_init(); if(!KISS_G(status)) return FAILURE; co = &KISS_G(obj_cache); MAKE_STD_ZVAL(*co); array_init(*co); co = &KISS_G(ls_called); MAKE_STD_ZVAL(*co); array_init(*co); KISS_G(ls_pre_disp) = NULL; KISS_G(ls_post_disp) = NULL; KISS_G(request) = NULL; // KISS_G(response) = NULL; return SUCCESS; } PHP_RSHUTDOWN_FUNCTION(kiss) { char ** path; zval ** co; kiss_request ** req; // kiss_response ** resp; kiss_status_free(KISS_G(status)); clear_var_pzval(co, obj_cache); clear_var_pzval(co, ls_called); clear_var_pzval(co, ls_pre_disp); clear_var_pzval(co, ls_post_disp); req = &KISS_G(request); if(*req) { kiss_request_free(*req); *req = NULL; } // resp = &KISS_G(response); if(*resp) { kiss_response_free(*resp); *resp = NULL; } return SUCCESS; } PHP_MINFO_FUNCTION(kiss) { php_info_print_table_start(); php_info_print_table_header(2, "kiss support", "enabled"); php_info_print_table_end(); // DISPLAY_INI_ENTRIES(); } PHP_METHOD(KissAction, __construct) { } // PHP_METHOD(KissAction, init) { } PHP_METHOD(KissListener, __construct) { } PHP_METHOD(KissListener, preDispatch) { } PHP_METHOD(KissListener, postDispatch) { } /** 包含并执行文件,参考include。 * proto mixed kiss_eval(string src[, long type]) * arg1 - 文件, * arg2 - eval的类型,缺省是KISS_REQUIRE, * return - 参考include,如有问题则返回NULL, * throw Exception(文件不存在) */ PHP_FUNCTION(kiss_eval) { zval * ret, * source; ulong type; type = KISS_REQUIRE; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l!", &source, &type) == FAILURE) { RETURN_NULL(); } if(Z_TYPE_P(source) != IS_STRING) RETURN_NULL(); if(!kiss_eval(type, source, &ret TSRMLS_CC)) { zend_throw_exception_ex(NULL, KISS_E_READFILE TSRMLS_CC, "文件%s不存在或不可读。", Z_STRVAL_P(source)); RETURN_NULL(); } if(ret) { RETURN_ZVAL(ret, 0, 1); } else { RETURN_NULL(); } } /** 设定运行状态,必须在kiss_front_start之前调用。 * proto mixed kiss_status(long key[, zval * value]) * arg1 - 代表状态字段的常量, * arg2 - 新值, * return - 旧值。 */ PHP_FUNCTION(kiss_status) { ulong k; zval * v = NULL; kiss_status * ks; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|z", &k, &v)==FAILURE) { return; } ks = KISS_G(status); #define status_bool(k) { / RETVAL_BOOL(ks->k); / if(v) ks->k = Z_BVAL_P(v); / return; / } #define status_string(k) { / if(ks->k) { / RETVAL_STRING(ks->k, 1); / efree(ks->k); / } / if(v) ks->k = estrdup(Z_STRVAL_P(v)); / return; / } switch(k) { case KISS_STATUS_PREFIX_CTL: status_bool(prefix_ctl); case KISS_STATUS_SHARE_GP: status_bool(share_gp); case KISS_STATUS_USE_PORT: status_bool(use_port); case KISS_STATUS_USE_CACHE: status_bool(use_cache); case KISS_STATUS_DISPATCH_TYPE: RETVAL_LONG(ks->dispatch_type); if(v) ks->dispatch_type = Z_LVAL_P(v); return; case KISS_STATUS_CLI_ROOT: status_string(cli_root); case KISS_STATUS_EXT_PROC: status_string(ext_proc); case KISS_STATUS_EXT_PATH: status_string(ext_path); case KISS_STATUS_DEFAULT_MDL: status_string(default_mdl); case KISS_STATUS_DEFAULT_CTL: status_string(default_ctl); case KISS_STATUS_DEFAULT_ACT: status_string(default_act); case KISS_STATUS_POSTFIX_CTL: status_string(postfix_ctl); case KISS_STATUS_POSTFIX_ACT: status_string(postfix_act); default: return; } } /** 手工设定模块分发目录。 * proto bool kiss_set_rule(string ctl_dir[, string mdl]) * arg1 - 控制器目录, * arg2 - 模块名,不提供或NULL表示缺省(全局)模块(空字符串表示), * return - 是否成功。 */ PHP_FUNCTION(kiss_set_rule) { char * mdl_name = "", * ctl_dir; uint mdl_name_len = 0, ctl_dir_len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s!", &ctl_dir, &ctl_dir_len, &mdl_name, &mdl_name_len) == FAILURE) { RETURN_BOOL(0); } RETURN_BOOL(kiss_set_rule(mdl_name, ctl_dir, ctl_dir_len TSRMLS_CC)); } /** 清除模块分发目录设置。 * proto void kiss_clear_rules(void) * TODO: 需要有对应的init函数。 */ PHP_FUNCTION(kiss_clear_rules) { zend_hash_destroy(&KISS_G(hosts_rw)); } /** 用于terminal调试。 */ PHP_FUNCTION(kiss_cli_test) { char * uri; uint uri_len; char * path = NULL; zend_bool flag; if(!sapi_module.name || strncmp(sapi_module.name, "cli", 3)) { kiss_e_throw("该函数只能用于CLI模式。", KISS_E_ONLYCLI); RETURN_FALSE; } if(!KISS_G(status)->cli_root || '/0'==*(KISS_G(status)->cli_root)) { kiss_e_throw("CLI模式需要kiss_status()提供基本目录。", KISS_E_NEEDROOT); RETURN_FALSE; } if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &uri, &uri_len)==FAILURE) { RETURN_FALSE; } if(!uri || !uri_len) { kiss_e_throw("必须提供测试用的URI!", KISS_E_NEEDURI); RETURN_BOOL(0); } #ifdef PHP_WIN32 if(!KISS_G(cfg_inited)) kiss_init_cfg(KISS_G(cfg_file) TSRMLS_CC); #endif if(!KISS_G(cfg_inited)) { kiss_e_throw("配置文件解析失败!", KISS_E_CFGFILE); RETURN_BOOL(0); } flag = kiss_request_init(uri, &path TSRMLS_CC); if(!flag) { if(path) efree(path); kiss_e_throw("初始化请求失败!", KISS_E_INITREQ); RETURN_BOOL(0); } flag = kiss_route_layer(path TSRMLS_CC); if(path) efree(path); if(!flag) { kiss_e_throw("路由失败!", KISS_E_ROUTE); RETURN_BOOL(0); } flag = kiss_dispatch(TSRMLS_C); if(!flag) { kiss_e_throw("分发失败!", KISS_E_DISPATCH); RETURN_BOOL(0); } RETURN_BOOL(1); } /** 启动前端控制器,如果未初始化,则用当前参数初始化。 * proto bool kiss_front_start(void) * throws Exception * return - 是否成功, * throws - 配置文件解析或路由或分发失败, * TODO: 加入更多的路由。 */ PHP_FUNCTION(kiss_front_start) { zend_bool flag; char * path = NULL; #ifdef PHP_WIN32 if(!KISS_G(cfg_inited)) kiss_init_cfg(KISS_G(cfg_file) TSRMLS_CC); #endif if(!KISS_G(cfg_inited)) { kiss_e_throw("配置文件解析失败!", KISS_E_CFGFILE); RETURN_BOOL(0); } flag = kiss_request_init(NULL, &path TSRMLS_CC); if(!flag) { if(path) efree(path); kiss_e_throw("初始化请求失败!", KISS_E_INITREQ); RETURN_BOOL(0); } flag = kiss_route_layer(path TSRMLS_CC); if(path) efree(path); if(!flag) { kiss_e_throw("路由失败!", KISS_E_ROUTE); RETURN_BOOL(0); } flag = kiss_dispatch(TSRMLS_C); if(!flag) { kiss_e_throw("分发失败!", KISS_E_DISPATCH); RETURN_BOOL(0); } RETURN_BOOL(1); } /** 设定Action或Controller的监听器,如果未初始化,则用当前参数初始化。 * NOTE: 在Controller调用。 * proto bool kiss_set_listener(string listener[, string item[, long type]]) * arg1 - 监听器类的名字, * arg2 - 监听目标,应是Action,空表示对整个Controller, * arg3 - 监听器类型(现在只有分发前和分发后),缺省是分发前, * return - 是否成功。*/ PHP_FUNCTION(kiss_set_listener) { char * item = NULL, * ls = NULL; uint item_len, ls_len, str_len; ulong ls_type; zval ** lsco, * mco, * cco, * aco, ** value; kiss_request * req; ls_type = KISS_LS_PRE_DISPATCH; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|sl!", &ls, &ls_len, &item, &item_len, &ls_type) == FAILURE) { RETURN_FALSE; } if(!ls || 0==ls_len) RETURN_BOOL(0); req = KISS_G(request); lsco = KISS_LS_PRE_DISPATCH==ls_type ? &KISS_G(ls_pre_disp) : &KISS_G(ls_post_disp); if(!*lsco) { MAKE_STD_ZVAL(*lsco); array_init(*lsco); } str_len = strlen(req->module); if(zend_hash_find(Z_ARRVAL_PP(lsco), req->module, str_len+1, (void**)&value) == FAILURE) { MAKE_STD_ZVAL(mco); array_init(mco); add_assoc_zval(*lsco, req->module, mco); } else mco = *value; str_len = strlen(req->controller); if(zend_hash_find(Z_ARRVAL_P(mco), req->controller, str_len+1, (void**)&value) == FAILURE) { MAKE_STD_ZVAL(cco); array_init(cco); add_assoc_zval(mco, req->controller, cco); } else cco = *value; if(!item || '/0' == *item) { // controller item = ""; item_len = 0; } if(zend_hash_find(Z_ARRVAL_P(cco), item, item_len+1, (void**)&value) == FAILURE) { MAKE_STD_ZVAL(aco); array_init(aco); add_assoc_zval(cco, item, aco); } else aco = *value; if(!zend_hash_exists(Z_ARRVAL_P(aco), ls, ls_len+1)) { add_assoc_long(aco, ls, 0); } RETURN_BOOL(1); } /** 服务器内跳转到其他的Action,如果是相同的控制器,则不再初始化,使用同一实利。 * 因为是函数调用方式,所以会返回调用点。 * proto bool kiss_forward(string action[, string controller[, string module[, array params]]]) * throws Exception * arg1 - Action, * arg2 - Controller,缺省为当前的控制器, * arg3 - Module,缺省为当前模块, * arg4 - 参数数组, * return - 是否成功, * throws - Action初始化或分发失败。 */ PHP_FUNCTION(kiss_forward) { char * action = NULL, * controller = NULL, * module = NULL; char * action2 = NULL, * controller2 = NULL, * module2 = NULL; uint act_len, ctl_len, mdl_len; zval * params = NULL; kiss_request * req; zval ** co; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ssz", &action, &act_len, &controller, &ctl_len, &module, &mdl_len, ¶ms) == FAILURE) { RETURN_FALSE; } if(!action || '/0' == action[0]) RETURN_BOOL(0); req = KISS_G(request); // efree(req->action); action2 = req->action; req->action = estrdup(action); if(controller && '/0' != controller[0]) { // efree(req->controller); controller2 = req->controller; req->controller = estrdup(controller); } if(module) { // efree(req->module); module2 = req->module; req->module = estrdup(module); } if(params && IS_ARRAY == Z_TYPE_P(params)) php_array_merge(Z_ARRVAL_P(req->query), Z_ARRVAL_P(params), 0 TSRMLS_CC); kiss_dispatch(TSRMLS_C); if(module2) { efree(req->module); req->module = module2; } if(controller2) { efree(req->controller); req->controller = controller2; } efree(req->action); req->action = action2; } /** 重定向到其他的地址。 * proto bool kiss_redirect(string action[, string controller[, string module[, long code]]]) * throws Exception * arg1 - Action或绝对路径, * arg2 - Controller,缺省为当前的控制器, * arg3 - Module,缺省为当前模块, * arg4 - 响应代码, * return - 是否成功, * throws - Action初始化或分发失败。 * TODO: 对于相对路径,需要加入参数。 */ PHP_FUNCTION(kiss_redirect) { // zend_bool replace = 1; sapi_header_line ctr = {0}; char * act = NULL, * ctl = NULL, * mdl = NULL, * lc_word; char * p1 = "http://", * p2 = "https://"/*, * protocol, url[1024]*/; uint a_len, c_len = 0, m_len = 0; kiss_request * req; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ssl", &act, &a_len, &ctl, &c_len, &mdl, &m_len, &ctr.response_code) == FAILURE) { RETURN_FALSE; } if(!act || !a_len) RETURN_BOOL(0); req = KISS_G(request); /*protocol = zend_getenv(SERVER_PROTOCOL, sizeof(SERVER_PROTOCOL)-1 TSRMLS_CC); if(!protocol || !*protocol) protocol = DEFAULT_SERVER_PROTOCOL;*/ lc_word = (char*)do_alloca(a_len + 1); zend_str_tolower_copy(lc_word, act, a_len); if(PATH_SEP == *act || !strncmp(p1, lc_word, sizeof(p1)) || !strncmp(p2, lc_word, sizeof(p2))) { // 绝对路径 ctr.line_len = spprintf(&(ctr.line), 0, "Location: %s", act); } else { // 相对路径 if(!mdl || !m_len) { mdl = req->module; } if(!ctl || !c_len) { ctl = req->controller; } ctr.line_len = spprintf(&(ctr.line), 0, "Location: /%s/%s/%s", mdl, ctl, act); } free_alloca(lc_word); sapi_header_op(SAPI_HEADER_REPLACE, &ctr TSRMLS_CC); efree(ctr.line); RETURN_BOOL(1); } /** 获取查询参数。 * proto mixed kiss_request_query([string key]) * arg1 - 参数的键,不提供则返回全部参数数组, * return - 键对应的值。 */ PHP_FUNCTION(kiss_request_query) { char * key = NULL; uint key_len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &key, &key_len) == FAILURE) { RETURN_FALSE; } kiss_request_query(key, KISS_G(request)->query, &return_value TSRMLS_CC); } /** 获取POST参数。 * proto mixed kiss_request_post([string key]) * arg1 - 参数的键,不提供则返回全部参数数组, * return - 键对应的值。 */ PHP_FUNCTION(kiss_request_post) { char * key = NULL; uint key_len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &key, &key_len) == FAILURE) { RETURN_FALSE; } kiss_request_query(key, KISS_G(request)->post, &return_value TSRMLS_CC); } /** 获取请求(QUERY+POST)参数,QUERY优先。 * proto mixed kiss_request_params([string key]) * arg1 - 参数的键,不提供则返回全部参数数组, * return - 键对应的值。 */ PHP_FUNCTION(kiss_request_params) { char * key = NULL; uint key_len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &key, &key_len) == FAILURE) { RETURN_FALSE; } kiss_request_query(key, KISS_G(request)->query, &return_value TSRMLS_CC); if(key && IS_NULL != Z_TYPE_P(return_value)) return; kiss_request_query(key, KISS_G(request)->post, &return_value TSRMLS_CC); //PHP_FN(kiss_request_query)(INTERNAL_FUNCTION_PARAM_PASSTHRU); //if(IS_NULL != Z_TYPE_P(return_value)) return; //PHP_FN(kiss_request_post)(INTERNAL_FUNCTION_PARAM_PASSTHRU); } /** 获取请求的信息。 * proto mixed kiss_request_query([long type]) * arg1 - 所需信息的类型常量,可组合, * return - 信息的数组,如需要的项不存在则为空字符串或空数组。 * TODO: 以后考虑接受任意数量参数。 */ PHP_FUNCTION(kiss_request_get) { ulong arg1; zval * ret; kiss_request * req; arg1 = KISS_REQ_ALL; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &arg1) == FAILURE) { RETURN_FALSE; } req = KISS_G(request); array_init(return_value); ret = return_value; if(KISS_REQ_METHOD & arg1) { add_assoc_string(ret, "method", req->method?req->method:"", 1); } if(KISS_REQ_HOST & arg1) { add_assoc_string(ret, "host", req->host?req->host:"", 1); } if(KISS_REQ_MODULE & arg1) { add_assoc_string(ret, "module", req->module?req->module:"", 1); } if(KISS_REQ_CONTROLLER & arg1) { add_assoc_string(ret, "controller", req->controller?req->controller:"", 1); } if(KISS_REQ_ACTION & arg1) { add_assoc_string(ret, "action", req->action?req->action:"", 1); } if(KISS_REQ_FRAGMENT & arg1) { add_assoc_string(ret, "fragment", req->fragment?req->fragment:"", 1); } if(KISS_REQ_QUERY & arg1) { ZVAL_ADDREF(req->query); // 引用 add_assoc_zval(ret, "query", req->query); } if(KISS_REQ_POST & arg1) { ZVAL_ADDREF(req->post); // 引用 add_assoc_zval(ret, "post", req->post); } } |
相关文章推荐
- 用Php扩展实现的简单框架1
- 用Php扩展实现的简单框架 - 2
- 用Php扩展实现的简单框架 - 4
- 用Php扩展实现的简单框架 - 6 - 使用示例
- 用Php扩展实现的简单框架 - 5
- 用Php扩展实现的简单框架 - 7 - v0.2
- php实现的一个简单json rpc框架实例
- 利用PHP SOAP扩展实现简单Web Services
- PHP MVC 框架的简单实现参考
- 在Yii框架中使用PHPExcel扩展从数据库导出excel文件功能的实现
- PHP扩展学习: 实现简单URL解析
- ThinkPHP3.2.3 实现定时计划的两种方式 -- 1. 框架的行为扩展 2.被定时计划调用的 PHP 脚本
- CodeIgniter源码阅读(6)Hook.php 扩展框架的实现原理
- 利用PHP SOAP扩展实现简单Web Services
- 一个php简单的框架实现,仅实现了简单路由层
- PHP基于mssql扩展远程连接MSSQL的简单实现方法
- 打造自己php的开发框架--php的MVC简单实现
- werkzeug实现简单Python web框架(5):扩展思路
- 常见 PHP ORM 框架与简单代码实现
- 【php框架学习】最简单的php mvc 模型框架实现