PHP - 类自动加载机制

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命令行模式会失效。

  • 0
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

mijar2016

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值