正文
PHP/composer开发中,我们只需要require ‘vendor/autoload.php’,然后就可以直接使用各种类了。那么这些类是如何加载的呢?其中有没有什么可以优化的点呢?
概览
PHP/composer下,类的加载主要到如下部分(还没有包括各个部分的初始化逻辑):
-
PHP中zend_lookup_class_ex
-
|-> EG(class_table)
-
|-> spl_autoload_call
-
|-> Composer\Autoload\ClassLoader::loadClass
-
|-> findFile
-
|-> class map lookup
-
|-> PSR-4 lookup
-
|-> PSR-0 lookup
PHP的类加载
首先,PHP在运行的时候,需要一个类,是通过
zend_lookup_class_ex
来找到这个类的相关信息的。
zend_lookup_class_ex
查找类的主要逻辑如下(假设类名字放到变量lc_name中):
-
ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, const zval *key, int use_autoload) /* {{{ */
-
{
-
// 1. 类名字转化为小写
-
if (ZSTR_VAL(name)[0] == '\\') {
-
lc_name = zend_string_alloc(ZSTR_LEN(name) - 1, 0);
-
zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1);
-
} else {
-
lc_name = zend_string_tolower(name);
-
}
-
-
// 2. 直接在class_table中查找
-
ce = zend_hash_find_ptr(EG(class_table), lc_name);
-
if (ce) {
-
if (!key) {
-
zend_string_release(lc_name);
-
}
-
return ce;
-
}
-
// 3. 如果没有autoload_func,则注册默认的__autoload
-
if (!EG(autoload_func)) {
-
zend_function *func = zend_hash_str_find_ptr(EG(function_table), ZEND_AUTOLOAD_FUNC_NAME, sizeof(ZEND_AUTOLOAD_FUNC_NAME) - 1);
-
if (func) {
-
EG(autoload_func) = func;
-
} else {
-
if (!key) {
-
zend_string_release(lc_name);
-
}
-
return NULL;
-
}
-
-
}
-
-
// 4. 加载ACLASS的过程中,又加载ACLASS,递归加载,直接找不到类
-
if (zend_hash_add_empty_element(EG(in_autoload), lc_name) == NULL) {
-
if (!key) {
-
zend_string_release(lc_name);
-
}
-
return NULL;
-
}
-
-
// 5. 调用autoload_func
-
ZVAL_STR_COPY(&fcall_info.function_name, EG(autoload_func)->common.function_name);
-
fcall_info.symbol_table = NULL;
-
-
zend_exception_save();
-
if ((zend_call_function(&fcall_info, &fcall_cache) == SUCCESS) && !EG(exception)) {
-
ce = zend_hash_find_ptr(EG(class_table), lc_name);
-
}
-
zend_exception_restore();
-
-
if (!key) {
-
zend_string_release(lc_name);
-
}
-
return ce;
-
}
-
lc_name转化成小写(这说明PHP中类名字不区分大小写)
-
然后在EG(class_table)找,如果找到,直接返回(我们自己注册的类,扩展注册的类都是这样找到的)
-
然后查看EG(autoload_func) ,如果没有则将
__autoload
注册上(值得注意的是,如果注册了EG(autoload_func),则不会走__autoload)
-
通过EG(in_autoload)判断是否递归加载了(EG(in_autoload)是一个栈,记载了那些类正在被autoload加载)
-
然后调用EG(autoload_func),并返回类信息
SPL扩展注册
刚刚可以看到,PHP只会调用EG(autoload_func),根本没有什么SPL的事情,那么SPL是如何让PHP调用自己的类加机制的呢?
首先,我去找SPL扩展的MINIT过程,结果发现其中并没有相关的逻辑。
出乎我的意料,这个注册过程在
spl_autoload_register
中完成:
-
PHP_FUNCTION(spl_autoload_register)
-
{
-
// 已经将SPL注册到PHP了,且当前用户要注册到spl的autoload函数已经注册,则跳过
-
if (SPL_G(autoload_functions) && zend_hash_exists(SPL_G(autoload_functions), lc_name)) {
-
if (!Z_ISUNDEF(alfi.closure)) {
-
Z_DELREF_P(&alfi.closure);
-
}
-
goto skip;
-
}
-
-
// 如果必要的话,初始化SPL_G(autoload_functions)
-
if (!SPL_G(autoload_functions)) {
-
ALLOC_HASHTABLE(SPL_G(autoload_functions));
-
zend_hash_init(SPL_G(autoload_functions), 1, NULL, autoload_func_info_dtor, 0);
-
}
-
-
// 如果之前已经注册了spl_autoload,那就将spl_autoload转移到autoload_functions中
-
spl_func_ptr = zend_hash_str_find_ptr(EG(function_table), "spl_autoload", sizeof("spl_autoload") - 1);
-
if (EG(autoload_func) == spl_func_ptr) { /* registered already, so we insert that first */
-
autoload_func_info spl_alfi;
-
-
spl_alfi.func_ptr = spl_func_ptr;
-
ZVAL_UNDEF(&spl_alfi.obj);
-
ZVAL_UNDEF(&spl_alfi.closure);
-
spl_alfi.ce = NULL;
-
zend_hash_str_add_mem(SPL_G(autoload_functions), "spl_autoload", sizeof("spl_autoload") - 1,
-
&spl_alfi, sizeof(autoload_func_info));
-
if (prepend && SPL_G(autoload_functions)->nNumOfElements > 1) {
-
/* Move the newly created element to the head of the hashtable */
-
HT_MOVE_TAIL_TO_HEAD(SPL_G(autoload_functions));
-
}
-
}
-
-
// 将用户要注册的函数,即lc_name,放到autoload_functions中
-
if (zend_hash_add_mem(SPL_G(autoload_functions), lc_name, &alfi, sizeof(autoload_func_info)) == NULL) {
-
if (obj_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) {
-
Z_DELREF(alfi.obj);
-
}
-
if (!Z_ISUNDEF(alfi.closure)) {
-
Z_DELREF(alfi.closure);
-
}
-
if (UNEXPECTED(alfi.func_ptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) {
-
zend_string_release(alfi.func_ptr->common.function_name);
-
zend_free_trampoline(alfi.func_ptr);
-
}
-
}
-
if (prepend && SPL_G(autoload_functions)->nNumOfElements > 1) {
-
/* Move the newly created element to the head of the hashtable */
-
HT_MOVE_TAIL_TO_HEAD(SPL_G(autoload_functions));
-
}
-
skip:
-
zend_string_release(lc_name);
-
}
-
-
// 根据autoload_functions的值,决定向PHP注册spl_autoload_call还是spl_autoload
-
if (SPL_G(autoload_functions)) {
-
EG(autoload_func) = zend_hash_str_find_ptr(EG(function_table), "spl_autoload_call", sizeof("spl_autoload_call") - 1);
-
} else {
-
EG(autoload_func) = zend_hash_str_find_ptr(EG(function_table), "spl_autoload", sizeof("spl_autoload") - 1);
-
}
-
-
RETURN_TRUE;
-
}
在composer环境下,这个函数的功能就是,将用户的autoload函数放到SPL_G(autoload_functions)中,且将spl_autoload_call注册到PHP中。
这样,PHP在找一个类的时候,就会调用spl_autoload_call了。
spl_autoload_call逻辑
spl_autoload_call
的逻辑很简单:
-
PHP_FUNCTION(spl_autoload_call)
-
{
-
if (SPL_G(autoload_functions)) {
-
HashPosition pos;
-
zend_ulong num_idx;
-
int l_autoload_running = SPL_G(autoload_running);
-
SPL_G(autoload_running) = 1;
-
lc_name = zend_string_alloc(Z_STRLEN_P(class_name), 0);
-
zend_str_tolower_copy(ZSTR_VAL(lc_name), Z_STRVAL_P(class_name), Z_STRLEN_P(class_name));
-
zend_hash_internal_pointer_reset_ex(SPL_G(autoload_functions), &pos);
-
// 遍历之前注册的autoload_functions
-
while (zend_hash_get_current_key_ex(SPL_G(autoload_functions), &func_name, &num_idx, &pos) == HASH_KEY_IS_STRING) {
-
alfi = zend_hash_get_current_data_ptr_ex(SPL_G(autoload_functions), &pos);
-
if (UNEXPECTED(alfi->func_ptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) {
-
zend_function *copy = emalloc(sizeof(zend_op_array));
-
-
memcpy(copy, alfi->func_ptr, sizeof(zend_op_array));
-
copy->op_array.function_name = zend_string_copy(alfi->func_ptr->op_array.function_name);
-
// 调用autoload_function
-
zend_call_method(Z_ISUNDEF(alfi->obj)? NULL : &alfi->obj, alfi->ce, ©, ZSTR_VAL(func_name), ZSTR_LEN(func_name), retval, 1, class_name, NULL);
-
} else {
-
zend_call_method(Z_ISUNDEF(alfi->obj)? NULL : &alfi->obj, alfi->ce, &alfi->func_ptr, ZSTR_VAL(func_name), ZSTR_LEN(func_name), retval, 1, class_name, NULL);
-
}
-
zend_exception_save();
-
if (retval) {
-
zval_ptr_dtor(retval);
-
retval = NULL;
-
}
-
// 如果调用结束之后,能在class_table找到类,则返回
-
if (zend_hash_exists(EG(class_table), lc_name)) {
-
break;
-
}
-
zend_hash_move_forward_ex(SPL_G(autoload_functions), &pos);
-
}
-
zend_exception_restore();
-
zend_string_free(lc_name);
-
SPL_G(autoload_running) = l_autoload_running;
-
} else {
-
/* do not use or overwrite &EG(autoload_func) here */
-
zend_call_method_with_1_params(NULL, NULL, NULL, "spl_autoload", NULL, class_name);
-
}
-
}
-
判断SPL_G(autoload_functions)存在
-
依次调用autoload_functions
-
如果调用完成后,这个类存在了,那就返回
至此,SPL的部分已经讲完了。我们来看看composer做了什么。