您的位置:首页 > 编程语言 > PHP开发

PHP - 类自动加载机制

2017-01-15 10:58 423 查看
PHP类自动加载机制被广泛运用到各个PHP框架中,在面向对象开发中,使用一套自动加载机制来管理我们的类库将会非常方便,从而减少使用前逐个引入包文件的痛苦。

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