请选择 进入手机版 | 继续访问电脑版

[PHP编程] PHP autoload机制案例详解

[复制链接]
查看112 | 回复14 | 2021-9-14 03:28:23 | 显示全部楼层 |阅读模式
  1. PHP在魔术函数__autoload()方法出现以前,如果你要在一个程序文件中实例化100个对象,那么你必须用include或者require包含进来100个类文件,或者你把这100个类定义在同一个类文件中——相信这个文件一定会非常大。但是__autoload()方法出来了,以后就不必为此大伤脑筋了,这个类会在你实例化对象之前自动加载制定的文件。
复制代码

1. autoload 机制概述

在利用 PHP的OO模式开发 体系 时,通常大家风俗 大将 每个类的实现都存放在一个单独的文件里,如许 会很容易 实现对类举行 复用,同时将来维护时也很便利。这也是OO计划 的基本头脑 之一。在PHP5之前,假如 必要 利用 一个类,只必要 直接利用 include/require将其包含进来即可。下面是一个现实 的例子:

  1. /* Person.class.php */
  2. <?php
  3. class Person {
  4. var $name, $age;
  5. function __construct ($name, $age)
  6. {
  7. $this->name = $name;
  8. $this->age = $age;
  9. }
  10. }
  11. ?>
  12. /* no_autoload.php */
  13. <?php
  14. require_once (”Person.class.php”);
  15. $person = new Person(”Altair”, 6);
  16. var_dump ($person);
  17. ?>
复制代码

在这个例子中,no-autoload.php文件必要 利用 Person类,它利用 了require_once将其包含,然后就可以直接利用 Person类来实例化一个对象。

但随着项目规模的不断扩大,利用 这种方式会带来一些隐含的标题 :假如 一个PHP文件必要 利用 很多别的 类,那么就必要 很多的require/include语句,如许 有大概 会造成遗漏或者包含进不必要的类文件。假如 大量的文件都必要 利用 别的 的类,那么要保证每个文件都包含准确 的类文件肯定是一个噩梦。

PHP5为这个标题 提供了一个办理 方案,这就是类的自动 装载(autoload)机制。autoload机制可以使得PHP程序有大概 在利用 类时才自动 包含类文件,而不是一开始就将全部 的类文件include进来,这种机制也称为lazy loading

下面是利用 autoload机制加载Person类的例子:

  1. /* autoload.php */
  2. <?php
  3. function __autoload($classname)
  4. {
  5. $classpath="./".$classname.'.class.php';
  6. if(file_exists($classpath))
  7. {
  8. require_once($classpath);
  9. }
  10. else
  11. {
  12. echo 'class file'.$classpath.'not found!';
  13. }
  14. }
  15. $person = new Person(”Altair”, 6);
  16. var_dump ($person);
  17. ?>
复制代码

通常PHP5在利用 一个类时,假如 发现这个类没有加载,就会自动 运行__autoload()函数,在这个函数中我们可以加载必要 利用 的类。在我们这个简单的例子中,我们直接将类名加上扩展名”.class.php”构成了类文件名,然后利用 require_once将其加载。从这个例子中,我们可以看出autoload至少要做三件变乱 ,第一件事是根据类名确定类文件名,第二件事是确定类文件地点 的磁盘路径(在我们的例子是最简单的环境 ,类与调用它们的PHP程序文件在同一个文件夹下),第三件事是将类从磁盘文件中加载到体系 中。第三步最简单,只必要 利用 include/require即可。要实现第一步,第二步的功能,必须在开发 时约定类名与磁盘文件的映射方法,只有如许 我们才能根据类名找到它对应的磁盘文件。

因此,当有大量的类文件要包含的时间 ,我们只要确定相应的规则,然后在__autoload()函数中,将类名与现实 的磁盘文件对应起来,就可以实现lazy loading的效果 。从这里我们也可以看出__autoload()函数的实现中最告急 的是类名与现实 的磁盘文件映射规则的实现。

但如今 标题 来了,假如 在一个体系 的实现中,假如 必要 利用 很多别的 的类库,这些类库大概 是由不同的开发 职员 编写的,其类名与现实 的磁盘文件的映射规则不尽雷同 。这时假如 要实现类库文件的自动 加载,就必须在__autoload()函数中将全部 的映射规则全部实现,如许 的话__autoload()函数有大概 会非常复杂,乃至 无法实现。末了 大概 会导致__autoload()函数非常 痴肥 ,这时即便可以或许 实现,也会给将来的维护和体系 服从 带来很大的负面影响。在这种环境 下,岂非 就没有更简单清楚 的办理 办法了吧?答案当然是:NO! 在看进一步的办理 方法之前,我们先来看一下PHP中的autoload机制是怎样 实现的。

2. PHP 的 autoload 机制的实现

我们知道,PHP文件的实行 分为两个独立的过程,第一步是将PHP文件编译成平常 称之为OPCODE的字节码序列(现实 上是编译成一个叫做zend_op_array的字节数组),第二步是由一个假造 机来实行 这些OPCODE。PHP的全部 举动 都是由这些OPCODE来实现的。因此,为了研究PHP中autoload的实现机制,我们将autoload.php文件编译成opcode,然后根据这些OPCODE来研究PHP在这过程中都做了些什么:

  1. /* autoload.php 编译后的OPCODE列表,是使用作者开发的OPDUMP工具
  2. * 生成的结果,可以到网站 http://www.phpinternals.com/ 下载该软件。
  3. */
  4. 1: <?php
  5. 2: // require_once (”Person.php”);
  6. 3:
  7. 4: function __autoload ($classname) {
  8. 0 NOP
  9. 0 RECV 1
  10. 5: if (!class_exists($classname)) {
  11. 1 SEND_VAR !0
  12. 2 DO_FCALL ‘class_exists' [extval:1]
  13. 3 BOOL_NOT $0 =>RES[~1]
  14. 4 JMPZ ~1, ->8
  15. 6: require_once ($classname. “.class.php”);
  16. 5 CONCAT !0, ‘.class.php' =>RES[~2]
  17. 6 INCLUDE_OR_EVAL ~2, REQUIRE_ONCE
  18. 7: }
  19. 7 JMP ->8
  20. 8: }
  21. 8 RETURN null
  22. 9:
  23. 10: $p = new Person('Fred', 35);
  24. 1 FETCH_CLASS ‘Person' =>RES[:0]
  25. 2 NEW :0 =>RES[$1]
  26. 3 SEND_VAL ‘Fred'
  27. 4 SEND_VAL 35
  28. 5 DO_FCALL_BY_NAME [extval:2]
  29. 6 ASSIGN !0, $1
  30. 11:
  31. 12: var_dump ($p);
  32. 7 SEND_VAR !0
  33. 8 DO_FCALL ‘var_dump' [extval:1]
  34. 13: ?>
复制代码

在autoload.php的第10行代码中我们必要 为类Person实例化一个对象。因此autoload机制肯定 会在该行编译后的opcode中有所表现 。从上面的第10行代码天生 的OPCODE中我们知道,在实例化对象Person时,起首 要实行 FETCH_CLASS指令。我们就从PHP对FETCH_CLASS指令的处理过程开始我们的探索之旅。

通过查阅PHP的源代码(我利用 的是PHP 5.3alpha2版本)可以发现如下的调用序列:

  1. ZEND_VM_HANDLER(109, ZEND_FETCH_CLASS, …) (zend_vm_def.h 1864行)
  2. => zend_fetch_class (zend_execute_API.c 1434行)
  3. =>zend_lookup_class_ex (zend_execute_API.c 964行)
  4. => zend_call_function(&fcall_info, &fcall_cache) (zend_execute_API.c 1040行)
复制代码

在末了 一步的调用之前,我们先看一下调用时的关键参数:

  1. /* 设置autoload_function变量值为”__autoload” */
  2. fcall_info.function_name = &autoload_function; // Ooops, 终于发现”__autoload”了
  3. fcall_cache.function_handler = EG(autoload_func); // autoload_func !
复制代码

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。
  2. 假如 autoload_func==NULL, 则查找体系 中是否定义有__autoload()函数,假如 没有,则报告错误并退出。
  3. 假如 定义了__autoload()函数,则实行 __autoload()尝试加载类,并返回加载效果 。
  4. 假如 autoload_func不为NULL,则直接实行 autoload_func指针指向的函数用来加载类。留意 此时并不检查__autoload()函数是否定义。

本相 终于大白,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机礼服 从 本身恰好 是用户计划 的自动 加载函数。假如 它不能高效的将类名与现实 的磁盘文件(留意 ,这里指现实 的磁盘文件,而不仅仅是文件名)对应起来,体系 将不得不做大量的文件是否存在(必要 在每个include path中包含的路径中去探求 )的判断 ,而判断 文件是否存在必要 做磁盘I/O操作,众所周知磁盘I/O操作的服从 很低,因此这才是使得autoload机礼服 从 降低的罪魁祸首!

因此,我们在体系 计划 时,必要 定义一套清楚 的将类名与现实 磁盘文件映射的机制。这个规则越简单越明白 ,autoload机制的服从 就越高。

  1. 结论:autoload机制并不是天然的效率低下,只有滥用autoload,设计不好的自动装载函数才会导致其效率的降低。
复制代码

到此这篇关于PHP autoload机制案例详解的文章就先容 到这了,更多干系 PHP autoload机制内容请搜索 脚本之家从前 的文章或继续欣赏 下面的干系 文章盼望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

avatar 爱过就是完全脖 | 2021-9-25 09:43:22 | 显示全部楼层
世界末日我都挺过去了,看到admin楼主我才知道为什么上帝留我到现在!
回复

使用道具 举报

avatar hedeafmz28 | 2021-9-26 22:06:20 | 显示全部楼层
admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的网站,他卖的服务器是永久的,我们的网站用 服务器都是在这家买的,你可以去试试。访问地址:http://fwq.mxswl.com
回复

使用道具 举报

avatar AriesHun | 2021-10-1 22:11:30 | 显示全部楼层
楼上的这是啥态度呢?
回复

使用道具 举报

avatar 汪晨霞 | 2021-10-3 01:04:03 | 显示全部楼层
论坛的帖子越来越有深度了!
回复

使用道具 举报

avatar 阿拉斯加他爸爸 | 2021-10-3 03:40:56 | 显示全部楼层
admin楼主很有经验啊!
回复

使用道具 举报

avatar 右脸破相gl | 2021-10-3 09:05:48 | 显示全部楼层
我回帖admin楼主给加积分吗?
回复

使用道具 举报

avatar 123457875 | 2021-10-3 09:09:01 | 显示全部楼层
学习雷锋,好好回帖!
回复

使用道具 举报

avatar 有个胖子他姓杨 | 2021-10-3 09:13:21 | 显示全部楼层
今天皮痒了?
回复

使用道具 举报

avatar 老鼠飞出亚洲言 | 2021-10-5 08:09:54 | 显示全部楼层
精神病院在通缉admin楼主!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则