其他特性
魔术方法
魔术方法是 PHP 独有的特性。魔术方法的实现方式与一般的方法几乎无异。区别就是部分魔术方法不只存在于 zend_class_entry
中的 function_table
,在 zend_class_entry
中也会直接存一份,所以会有如图6-6所示的结构图(请注意 __get
方法的存储位置)。

自动加载
自动加载是依据用户实现的规则来加载 PHP 文件,实现类的加载。比如现在新建一个 PHP 7 类型的对象,如果还未加载这个类,且用户没有实现自动加载方法,或是实现的自动加载方法没能把 PHP 7 这个类加载进来,则会报语法错误。
根据这个逻辑,能迅速猜测到自动加载的实现原理:当用到一个类时,如果在 EG(class_table)
中没有找到这个类,则去调用用户实现的自动加载方法。调用后到 EG(class_table)
中查找此类,如果此时仍然没有,则抛出语法错误。
PHP 7 中提供了两种自动加载方式:__autoload()
和 spl_autoload_register()
。
-
__autoload
:这个自动加载方法比较简单,在 PHP 中实现了此方法后,会存储在EG(autoload_func)
中。当需要加载新的类时,内核会调用此方法加载类。此外,内核自己实现了__autoload
的默认版本PHP_FUNCTION(spl_autoload)
。 -
spl_autoload_register
:这种加载方法比较高级。除了可以实现多个加载逻辑之外,还可以设置优先级(把加载逻辑放在最高优先级或者最低优先级)。知名管理工具Composer
便主要是靠spl_autoload_register
来实现的。
而且,还可以通过 spl_autoload_unregister
来删除某个加载逻辑。
现在我们知道,通过 spl_autoload_register
来进行自动加载,大体有以下几个特征:多个、优先级、可查找(查找后删除)。那么之前学到的内核数据结构有没有可以满足这个需求的?答案是有,内核强大的 HashTable 完全可以满足这些需求。
事实上,内核确实就是用 HashTable 来实现的。内核使用 HashTable 存储在全局变量 SPL_G(autoload_functions)
中。SPL_G(autoload_functions)
初始化为 NULL,当程序第一次调用 spl_autoload_register()
方法来增加自动加载逻辑时,内核会对其进行初始化。
由于 spl_autoload_register()
可以通过方法、类的普通方法甚至是闭包来实现自动加载,所以这个 HashTable 的 value
用 zend_function
或用 zval
都不能满足需求,为了满足需求,内核定义了一个结构体 autoload_func_info
,用于存储用户实现的自动加载逻辑。
typedef struct {
zend_function *func_ptr;
zval obj;
zval closure;
zend_class_entry *ce;
} autoload_func_info;
可以看出,此 value
类型并非联合体,实现起来很简单。
此外,需要说明的是,无论实现了哪种自动加载,都是在 PHP_FUNCTION(spl_autoload_call)
中对自动加载方法进行调用。如果全局变量 SPL_G(autoload_functions)
已初始化,则按顺序调用加载逻辑,直到此类正确加载。否则直接调用 __autoload
实现。
if (SPL_G(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 (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);
}