PHP - 类自动加载机制
2017-01-15 10:58
423 查看
PHP类自动加载机制被广泛运用到各个PHP框架中,在面向对象开发中,使用一套自动加载机制来管理我们的类库将会非常方便,从而减少使用前逐个引入包文件的痛苦。
PHP提供了两种注册自动加载的方式:__autoload 和 spl_autoload_register。下面将介绍这两个函数在PHP的实现。
__autoload
当我们在PHP脚本定义__autoload函数时,PHP在编译阶段调用
然后,在
spl_autoload_register
1.判断spl_autoload_register的参数名是不是
2.判断该函数是否已被注册到SPL_G(autoload_functions) hash表。
3.最后,将spl_autoload_call/spl_autoload绑定到EG(autoload_func)中。
spl_autoload_call/spl_autoload:遍历被注册在SPL_G(autoload_functions)的函数,逐个调用实现相应的加载类。
自动加载原理
当我们使用class_exists或者实例化一个类时,PHP内部通过
fcall_info:包含EG(function_table)的
fcall_cache:包含
根据
总结
1.
2. 自动加载机制在cli命令行模式会失效。
PHP提供了两种注册自动加载的方式:__autoload 和 spl_autoload_register。下面将介绍这两个函数在PHP的实现。
__autoload
当我们在PHP脚本定义__autoload函数时,PHP在编译阶段调用
zend_do_begin_function_declaration将该函数加入到函数符号表( function_table )中。
void zend_do_begin_function_declaration(znode *function_token, znode *function_name, int is_method, int return_reference, znode *fn_flags_znode TSRMLS_DC) { //...省略部分代码... zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); if (CG(current_namespace)) { znode tmp; tmp.u.constant = *CG(current_namespace); zval_copy_ctor(&tmp.u.constant); zend_do_build_namespace_name(&tmp, &tmp, function_name TSRMLS_CC); op_array.function_name = Z_STRVAL(tmp.u.constant); efree(lcname); name_len = Z_STRLEN(tmp.u.constant); lcname = zend_str_tolower_dup(Z_STRVAL(tmp.u.constant), name_len); } opline->opcode = ZEND_DECLARE_FUNCTION; opline->op1.op_type = IS_CONST; build_runtime_defined_function_key(&opline->op1.u.constant, lcname, name_len TSRMLS_CC); opline->op2.op_type = IS_CONST; opline->op2.u.constant.type = IS_STRING; opline->op2.u.constant.value.str.val = lcname; opline->op2.u.constant.value.str.len = name_len; Z_SET_REFCOUNT(opline->op2.u.constant, 1); opline->extended_value = ZEND_DECLARE_FUNCTION; zend_hash_update(CG(function_table), opline->op1.u.constant.value.str.val, opline->op1.u.constant.value.str.len, &op_array, sizeof(zend_op_array), (void **) &CG(active_op_array)); //...省略部分代码... }
然后,在
zend_do_end_function_declaration函数对__autoload函数语法进行检查。
void zend_do_end_function_declaration(const znode *function_token TSRMLS_DC) { //...省略部分代码... name_len = strlen(CG(active_op_array)->function_name); zend_str_tolower_copy(lcname, CG(active_op_array)->function_name, MIN(name_len, sizeof(lcname)-1)); lcname[sizeof(lcname)-1] = '\0'; if (name_len == sizeof(ZEND_AUTOLOAD_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_AUTOLOAD_FUNC_NAME, sizeof(ZEND_AUTOLOAD_FUNC_NAME)) && CG(active_op_array)->num_args != 1) { zend_error(E_COMPILE_ERROR, "%s() must take exactly 1 argument", ZEND_AUTOLOAD_FUNC_NAME); } //...省略部分代码... }
spl_autoload_register
spl_autoload_register是PHP内置的函数,其代码定义位于php_spl.c。
PHP_FUNCTION(spl_autoload_register) { char *func_name, *error = NULL; int func_name_len; char *lc_name = NULL; zval *zcallable = NULL; zend_bool do_throw = 1; zend_bool prepend = 0; zend_function *spl_func_ptr; autoload_func_info alfi; zval *obj_ptr; zend_fcall_info_cache fcc; if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "|zbb", &zcallable, &do_throw, &prepend) == FAILURE) { return; } if (ZEND_NUM_ARGS()) { //如果注册的参数为spl_autoload_call,则退出 if (Z_TYPE_P(zcallable) == IS_STRING) { if (Z_STRLEN_P(zcallable) == sizeof("spl_autoload_call") - 1) { if (!zend_binary_strcasecmp(Z_STRVAL_P(zcallable), sizeof("spl_autoload_call"), "spl_autoload_call", sizeof("spl_autoload_call"))) { if (do_throw) { zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Function spl_autoload_call() cannot be registered"); } RETURN_FALSE; } } } if (!zend_is_callable_ex(zcallable, NULL, IS_CALLABLE_STRICT, &func_name, &func_name_len, &fcc, &error TSRMLS_CC)) { alfi.ce = fcc.calling_scope; alfi.func_ptr = fcc.function_handler; obj_ptr = fcc.object_ptr; if (Z_TYPE_P(zcallable) == IS_ARRAY) { if (!obj_ptr && alfi.func_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) { if (do_throw) { zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Passed array specifies a non static method but no object (%s)", error); } if (error) { efree(error); } efree(func_name); RETURN_FALSE; } else if (do_throw) { zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Passed array does not specify %s %smethod (%s)", alfi.func_ptr ? "a callable" : "an existing", !obj_ptr ? "static " : "", error); } if (error) { efree(error); } efree(func_name); RETURN_FALSE; } else if (Z_TYPE_P(zcallable) == IS_STRING) { if (do_throw) { zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Function '%s' not %s (%s)", func_name, alfi.func_ptr ? "callable" : "found", error); } if (error) { efree(error); } efree(func_name); RETURN_FALSE; } else { if (do_throw) { zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Illegal value passed (%s)", error); } if (error) { efree(error); } efree(func_name); RETURN_FALSE; } } alfi.closure = NULL; alfi.ce = fcc.calling_scope; alfi.func_ptr = fcc.function_handler; obj_ptr = fcc.object_ptr; if (error) { efree(error); } lc_name = safe_emalloc(func_name_len, 1, sizeof(long) + 1); zend_str_tolower_copy(lc_name, func_name, func_name_len); efree(func_name); //对象方法 if (Z_TYPE_P(zcallable) == IS_OBJECT) { alfi.closure = zcallable; Z_ADDREF_P(zcallable); lc_name = erealloc(lc_name, func_name_len + 2 + sizeof(zend_object_handle)); memcpy(lc_name + func_name_len, &Z_OBJ_HANDLE_P(zcallable), sizeof(zend_object_handle)); func_name_len += sizeof(zend_object_handle); lc_name[func_name_len] = '\0'; } //判断lc_name是否在SPL_G(autoload_functions)中 if (SPL_G(autoload_functions) && zend_hash_exists(SPL_G(autoload_functions), (char*)lc_name, func_name_len+1)) { if (alfi.closure) { Z_DELREF_P(zcallable); } goto skip; } if (obj_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) { /* add object id to the hash to ensure uniqueness, for more reference look at bug #40091 */ lc_name = erealloc(lc_name, func_name_len + 2 + sizeof(zend_object_handle)); memcpy(lc_name + func_name_len, &Z_OBJ_HANDLE_P(obj_ptr), sizeof(zend_object_handle)); func_name_len += sizeof(zend_object_handle); lc_name[func_name_len] = '\0'; alfi.obj = obj_ptr; Z_ADDREF_P(alfi.obj); } else { alfi.obj = NULL; } if (!SPL_G(autoload_functions)) { //初始化SPL_G(autoload_functions) ALLOC_HASHTABLE(SPL_G(autoload_functions)); zend_hash_init(SPL_G(autoload_functions), 1, NULL, (dtor_func_t) autoload_func_info_dtor, 0); } zend_hash_find(EG(function_table), "spl_autoload", sizeof("spl_autoload"), (void **) &spl_func_ptr); if (EG(autoload_func) == spl_func_ptr) { autoload_func_info spl_alfi; spl_alfi.func_ptr = spl_func_ptr; spl_alfi.obj = NULL; spl_alfi.ce = NULL; spl_alfi.closure = NULL; zend_hash_add(SPL_G(autoload_functions), "spl_autoload", sizeof("spl_autoload"), &spl_alfi, sizeof(autoload_func_info), NULL); if (prepend && SPL_G(autoload_functions)->nNumOfElements > 1) { HT_MOVE_TAIL_TO_HEAD(SPL_G(autoload_functions)); } } zend_hash_add(SPL_G(autoload_functions), lc_name, func_name_len+1, &alfi.func_ptr, sizeof(autoload_func_info), NULL); if (prepend && SPL_G(autoload_functions)->nNumOfElements > 1) { HT_MOVE_TAIL_TO_HEAD(SPL_G(autoload_functions)); } skip: efree(lc_name); } if (SPL_G(autoload_functions)) { zend_hash_find(EG(function_table), "spl_autoload_call", sizeof("spl_autoload_call"), (void **) &EG(autoload_func)); } else { zend_hash_find(EG(function_table), "spl_autoload", sizeof("spl_autoload"), (void **) &EG(autoload_func)); } RETURN_TRUE; }
spl_autoload_register的流程如下:
1.判断spl_autoload_register的参数名是不是
spl_autoload_call;如果是,则跳出。
2.判断该函数是否已被注册到SPL_G(autoload_functions) hash表。
3.最后,将spl_autoload_call/spl_autoload绑定到EG(autoload_func)中。
spl_autoload_call/spl_autoload:遍历被注册在SPL_G(autoload_functions)的函数,逐个调用实现相应的加载类。
自动加载原理
当我们使用class_exists或者实例化一个类时,PHP内部通过
zend_lookup_class查找类,而
zend_lookup_class实际上调用的是
zend_lookup_class_ex。当调用的类不在class_table中,则会调用注册的自动加载函数查找。
ZEND_API int zend_lookup_class_ex(const char *name, int name_length, int use_autoload, zend_class_entry ***ce TSRMLS_DC) { //...省略部分代码... ZVAL_STRINGL(&autoload_function, ZEND_AUTOLOAD_FUNC_NAME, sizeof(ZEND_AUTOLOAD_FUNC_NAME) - 1, 0); ALLOC_ZVAL(class_name_ptr); INIT_PZVAL(class_name_ptr); if (name[0] == '\\') { ZVAL_STRINGL(class_name_ptr, name+1, name_length-1, 1); } else { ZVAL_STRINGL(class_name_ptr, name, name_length, 1); } args[0] = &class_name_ptr; fcall_info.size = sizeof(fcall_info); fcall_info.function_table = EG(function_table); fcall_info.function_name = &autoload_function; fcall_info.symbol_table = NULL; fcall_info.retval_ptr_ptr = &retval_ptr; fcall_info.param_count = 1; fcall_info.params = args; fcall_info.object_ptr = NULL; fcall_info.no_separation = 1; fcall_cache.initialized = EG(autoload_func) ? 1 : 0; fcall_cache.function_handler = EG(autoload_func); fcall_cache.calling_scope = NULL; fcall_cache.called_scope = NULL; fcall_cache.object_ptr = NULL; zend_exception_save(TSRMLS_C); //调用自动加载函数进行查找 retval = zend_call_function(&fcall_info, &fcall_cache TSRMLS_CC); zend_exception_restore(TSRMLS_C); EG(autoload_func) = fcall_cache.function_handler; //...省略部分代码... return retval; }
zend_call_function函数:回调注册的自动加载函数,该函数传递两个参数,fcall_info、fcall_cache。
fcall_info:包含EG(function_table)的
autoload_function信息,即通过”__autoload”注册的自动加载方式。
fcall_cache:包含
EG(autoload_func)信息,即通过spl_autoload_register注册自动加载方式。
int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache TSRMLS_DC) { //...省略部分代码... if (!fci_cache || !fci_cache->initialized) { zend_fcall_info_cache fci_cache_local; char *callable_name; char *error = NULL; if (!fci_cache) { fci_cache = &fci_cache_local; } if (!zend_is_callable_ex(fci->function_name, fci->object_ptr, IS_CALLABLE_CHECK_SILENT, &callable_name, NULL, fci_cache, &error TSRMLS_CC)) { if (error) { zend_error(E_WARNING, "Invalid callback %s, %s", callable_name, error); efree(error); } if (callable_name) { efree(callable_name); } return FAILURE; } else if (error) { if (error[0] >= 'a' && error[0] <= 'z') { error[0] += ('A' - 'a'); } zend_error(E_STRICT, "%s", error); efree(error); } efree(callable_name); } EX(function_state).function = fci_cache->function_handler; calling_scope = fci_cache->calling_scope; called_scope = fci_cache->called_scope; fci->object_ptr = fci_cache->object_ptr; EX(object) = fci->object_ptr; //...省略部分代码... }
zend_is_callable_ex检查fci->function_name的合法性,并赋值给
fci_cache。
根据
zend_call_function代码逻辑,我们可以看出,系统优先使用fci_cache。当fci_cache未设置时,则使用
fci。换句话说,优先使用
spl_autoload_register注册的加载机制;如果未定义
spl_autoload_register,则使用
__autoload加载。
总结
1.
spl_autoload_register会覆盖
__autoload的加载机制。
2. 自动加载机制在cli命令行模式会失效。
相关文章推荐
- PHP的类自动加载机制
- php自动加载机制autoload
- PHP autoload与spl_autoload自动加载机制的深入理解
- 说说PHP的autoLoad自动加载机制
- PHP的类自动加载机制
- PHP自动加载__autoload的工作机制
- PHP autoload和spl_autoload自动加载机制详解
- PHP的类自动加载机制
- PHP自动加载__autoload的工作机制
- 说说PHP的autoLoad自动加载机制
- PHPdragon framework(1) PHP中类的自动加载机制
- PHP的类自动加载机制
- php自动加载机制的深入分析
- PHP的autoload自动加载机制使用说明
- PHP的类自动加载机制
- php类自动加载机制
- PHP的类自动加载机制
- php _autoload自动加载类与机制分析
- php自动加载机制的深入分析
- PHP autoload与spl_autoload自动加载机制的深入理解