在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
/* Person.class.php */ /* no_autoload.php */ 在这个例子中,no-autoload.php文件需要使用Person类,它使用了require_once将其包含,然后就可以直接使用Person类来实例化一个对象。 但随着项目规模的不断扩大,使用这种方式会带来一些隐含的问题:如果一个PHP文件需要使用很多其它类,那么就需要很多的require/include语句,这样有可能会造成遗漏或者包含进不必要的类文件。如果大量的文件都需要使用其它的类,那么要保证每个文件都包含正确的类文件肯定是一个噩梦。 PHP5为这个问题提供了一个解决方案,这就是类的自动装载(autoload)机制。autoload机制可以使得PHP程序有可能在使用类时才自动包含类文件,而不是一开始就将所有的类文件include进来,这种机制也称为lazy loading。 下面是使用autoload机制加载Person类的例子: /* autoload.php */ 因此,当有大量的类文件要包含的时候,我们只要确定相应的规则,然后在__autoload()函数中,将类名与实际的磁盘文件对应起来,就可以实现lazy loading的效果。从这里我们也可以看出__autoload()函数的实现中最重要的是类名与实际的磁盘文件映射规则的实现。 (2) PHP的autoload机制的实现 我们知道,PHP文件的执行分为两个独立的过程,第一步是将PHP文件编译成普通称之为OPCODE的字节码序列(实际上是编译成一个叫做zend_op_array的字节数组),第二步是由一个虚拟机来执行这些OPCODE。PHP的所有行为都是由这些OPCODE来实现的。因此,为了研究PHP中autoload的实现机制,我们将autoload.php文件编译成opcode,然后根据这些OPCODE来研究PHP在这过程中都做了些什么: /* autoload.php 编译后的OPCODE列表,是使用作者开发的OPDUMP工具 在autoload.php的第10行代码中我们需要为类Person实例化一个对象。因此autoload机制一定会在该行编译后的opcode中有所体现。从上面的第10行代码生成的OPCODE中我们知道,在实例化对象Person时,首先要执行FETCH_CLASS指令。我们就从PHP对FETCH_CLASS指令的处理过程开始我们的探索之旅。 通过查阅PHP的源代码(我使用的是PHP 5.3alpha2版本)可以发现如下的调用序列: ZEND_VM_HANDLER(109, ZEND_FETCH_CLASS, …) (zend_vm_def.h 1864行) 在最后一步的调用之前,我们先看一下调用时的关键参数: /* 设置autoload_function变量值为”__autoload” */ zend_call_function是Zend Engine中最重要的函数之一,其主要功能是执行用户在PHP程序中自定义的函数或者PHP本身的库函数。zend_call_function有两个重要的指针形参数fcall_info, fcall_cache,它们分别指向两个重要的结构,一个是zend_fcall_info, 另一个是zend_fcall_info_cache。zend_call_function主要工作流程如下:如果fcall_cache.function_handler指针为NULL,则尝试查找函数名为fcall_info.function_name的函数,如果存在的话,则执行之;如果fcall_cache.function_handler不为NULL,则直接执行fcall_cache.function_handler指向的函数。 现在我们清楚了,PHP在实例化一个对象时(实际上在实现接口,使用类常数或类中的静态变量,调用类中的静态方法时都会如此),首先会在系统中查找该类(或接口)是否存在,如果不存在的话就尝试使用autoload机制来加载该类。而autoload机制的主要执行过程为: (1) 检查执行器全局变量函数指针autoload_func是否为NULL。 真相终于大白,PHP提供了两种方法来实现自动装载机制,一种我们前面已经提到过,是使用用户定义的__autoload()函数,这通常在PHP源程序中来实现;另外一种就是设计一个函数,将autoload_func指针指向它,这通常使用C语言在PHP扩展中实现。如果既实现了__autoload()函数,又实现了autoload_func(将autoload_func指向某一PHP函数),那么只执行autoload_func函数。 (3) SPL autoload机制的实现 SPL是Standard PHP Library(标准PHP库)的缩写。它是PHP5引入的一个扩展库,其主要功能包括autoload机制的实现及包括各种Iterator接口或类。SPL autoload机制的实现是通过将函数指针autoload_func指向自己实现的具有自动装载功能的函数来实现的。SPL有两个不同的函数spl_autoload, spl_autoload_call,通过将autoload_func指向这两个不同的函数地址来实现不同的自动加载机制。 spl_autoload是SPL实现的默认的自动加载函数,它的功能比较简单。它可以接收两个参数,第一个参数是$class_name,表示类名,第二个参数$file_extensions是可选的,表示类文件的扩展名,可以在$file_extensions中指定多个扩展名,护展名之间用分号隔开即可;如果不指定的话,它将使用默认的扩展名.inc或.php。spl_autoload首先将$class_name变为小写,然后在所有的include path中搜索$class_name.inc或$class_name.php文件(如果不指定$file_extensions参数的话),如果找到,就加载该类文件。你可以手动使用spl_autoload(”Person”, “.class.php”)来加载Person类。实际上,它跟require/include差不多,不同的它可以指定多个扩展名。 怎样让spl_autoload自动起作用呢,也就是将autoload_func指向spl_autoload?答案是使用spl_autoload_register函数。在PHP脚本中第一次调用spl_autoload_register()时不使用任何参数,就可以将autoload_func指向spl_autoload。 通过上面的说明我们知道,spl_autoload的功能比较简单,而且它是在SPL扩展中实现的,我们无法扩充它的功能。如果想实现自己的更灵活的自动加载机制怎么办呢?这时,spl_autoload_call函数闪亮登场了。 我们先看一下spl_autoload_call的实现有何奇妙之处。在SPL模块内部,有一个全局变量autoload_functions,它本质上是一个HashTable,不过我们可以将其简单的看作一个链表,链表中的每一个元素都是一个函数指针,指向一个具有自动加载类功能的函数。spl_autoload_call本身的实现很简单,只是简单的按顺序执行这个链表中每个函数,在每个函数执行完成后都判断一次需要的类是否已经加载,如果加载成功就直接返回,不再继续执行链表中的其它函数。如果这个链表中所有的函数都执行完成后类还没有加载,spl_autoload_call就直接退出,并不向用户报告错误。因此,使用了autoload机制,并不能保证类就一定能正确的自动加载,关键还是要看你的自动加载函数如何实现。 那么自动加载函数链表autoload_functions是谁来维护呢?就是前面提到的spl_autoload_register函数。它可以将用户定义的自动加载函数注册到这个链表中,并将autoload_func函数指针指向spl_autoload_call函数(注意有一种情况例外,具体是哪种情况留给大家思考)。我们也可以通过spl_autoload_unregister函数将已经注册的函数从autoload_functions链表中删除。 上节说过,当autoload_func指针非空时,就不会自动执行__autoload()函数了,现在autoload_func已经指向了spl_autoload_call,如果我们还想让__autoload()函数起作用应该怎么办呢?当然还是使用spl_autoload_register(__autoload)调用将它注册到autoload_functions链表中。 现在回到第一节最后的问题,我们有了解决方案:根据每个类库不同的命名机制实现各自的自动加载函数,然后使用spl_autoload_register分别将其注册到SPL自动加载函数队列中就可了。这样我们就不用维护一个非常复杂的__autoload函数了。 (4) autoload效率问题及对策 使用autoload机制时,很多人的第一反应就是使用autoload会降低系统效率,甚至有人干脆提议为了效率不要使用autoload。在我们了解了autoload实现的原理后,我们知道autoload机制本身并不是影响系统效率的原因,甚至它还有可能提高系统效率,因为它不会将不需要的类加载到系统中。 因此,我们在系统设计时,需要定义一套清晰的将类名与实际磁盘文件映射的机制。这个规则越简单越明确,autoload机制的效率就越高。 结论:autoload机制并不是天然的效率低下,只有滥用autoload,设计不好的自动装载函数才会导致其效率的降低。 |
2022-07-18
2022-08-17
2022-11-06
2022-08-17
2022-08-15
请发表评论