PHP autoload机制详解
PHP autoload机制详解
转载自 jeakccc
PHP autoload机制详解
(1) autoload机制概述
在使用PHP的OO模式开发系统时,通常大家习惯上将每个类的实现都存放在一个单独的文件里,这样会很容易实现对类进行复用,同时将来维护时也很便利。这 也是OO设计的基本思想之一。在PHP5之前,如果需要使用一个类,只需要直接使用include/require将其包含进来即可。下面是一个实际的例 子:
/* Person.class.php */
<?php
class Person {
var $name, $age;
function __construct ($name, $age)
{
$this->name = $name;
$this->age = $age;
}
}
?>
/* no_autoload.php */
<?php
require_once (”Person.class.php”);
$person = new Person(”Altair”, 6);
var_dump ($person);
?>
在这个例子中,no-autoload.php文件需要使用Person类,它使用了require_once将其包含,然后就可以直接使用 Person类来实例化一个对象。
但 随着项目规模的不断扩大,使用这种方式会带来一些隐含的问题:如果一个PHP文件需要使用很多其它类,那么就需要很多的require/include语 句,这样有可能会造成遗漏或者包含进不必要的类文件。如果大量的文件都需要使用其它的类,那么要保证每个文件都包含正确的类文件肯定是一个噩梦。
PHP5为这个问题提供了一个解决方案,这就是类的自动装载(autoload)机制。autoload机制可以使得PHP程序有可能在使用类时才 自动包含类文件,而不是一开始就将所有的类文件include进来,这种机制也称为lazy loading。
下面是使用autoload机制加载Person类的例子:
/* autoload.php */
<?php
function __autoload($classname) {
require_once ($classname . “class.php”);
}
$person = new Person(”Altair”, 6);
var_dump ($person);
?>
通常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在这过程中都做了 些什么:
/* autoload.php 编译后的OPCODE列表,是使用作者开发的OPDUMP工具
* 生成的结果,可以到网站 http://www.phpinternals.com/ 下载该软件。
*/
1: <?php
2: // require_once (”Person.php”);
3:
4: function __autoload ($classname) {
0 NOP
0 RECV 1
5: if (!class_exists($classname)) {
1 SEND_VAR !0
2 DO_FCALL ‘class_exists’ [extval:1]
3 BOOL_NOT $0 =>RES[~1]
4 JMPZ ~1, ->8
6: require_once ($classname. “.class.php”);
5 CONCAT !0, ‘.class.php’ =>RES[~2]
6 INCLUDE_OR_EVAL ~2, REQUIRE_ONCE
7: }
7 JMP ->8
8: }
8 RETURN null
9:
10: $p = new Person(’Fred’, 35);
1 FETCH_CLASS ‘Person’ =>RES[:0]
2 NEW :0 =>RES[$1]
3 SEND_VAL ‘Fred’
4 SEND_VAL 35
5 DO_FCALL_BY_NAME [extval:2]
6 ASSIGN !0, $1
11:
12: var_dump ($p);
7 SEND_VAR !0
8 DO_FCALL ‘var_dump’ [extval:1]
13: ?>
在 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行)
=> zend_fetch_class (zend_execute_API.c 1434行)
=>zend_lookup_class_ex (zend_execute_API.c 964行)
=> zend_call_function(&fcall_info, &fcall_cache) (zend_execute_API.c 1040行)
在最后一步的调用之前,我们先看一下调用时的关键参数:
/* 设置autoload_function变量值为”__autoload” */
fcall_info.function_name = &autoload_function; // Ooops, 终于发现”__autoload”了
…
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机制的效率就越高。
结论:autoload机制并不是天然的效率低下,只有滥用autoload,设计不好的自动装载函数才会导致其效率的降低。
ps:
一、spl_autoload_register将spl_autoload(),spl_autoload_call(),__autoload注册到SPL_autoload函数栈中。
1. __autoload($className)
Example:
function __autoload($className){
include ($className.".php");}
当在本页中找不到类时,自动运行__autoload函数,执行函数中代码。
2. Spl_autoload()
Example: spl_autoload('test2','.php');
可以接收类名与扩展名二个参数,不需要再写php代码直接可用。可指定多个扩展名,用逗号分开。
3.问题:怎样将autoload函数加入到函数注册列表中,是否在同一页面执行,所有自动加载函数共用??
相关文章:

有关博客的一些断想
作者:朱金灿来源:http://blog.csdn.net/clever101随着微博、微信等短平快社交媒体的兴起,文字相对严肃的博客毫无疑问受到很大的冲击。我在想博客会不会因此而消亡呢。我相信不会,因为喜欢轻快的文字固然是人类的天性,…

pythonl学习笔记——爬虫的基本常识
1 robots协议 Robots协议(也称为爬虫协议、机器人协议等)的全称是“网络爬虫排除标准”(Robots Exclusion Protocol),网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。 如: …

hibernate相关收集
2019独角兽企业重金招聘Python工程师标准>>> 1、Hibernate SQL方言 如果出现如下错误,则可能是Hibernate SQL方言 (hibernate.dialect)设置不正确。 Caused by: java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC][SQLServer]last_ins…

盘一盘 2021 年程序员们喜欢的网站数据
作者 | 周萝卜来源 | 萝卜大杂烩世界上流量最大的网站有哪些,也许我们都能脱口而出,比如 Google,YouTube,Facebook 还有 PxxnHub 等等,今天我们就通过多个维度来看看,那些叱咤全球的流量网站!数…

烽火18台系列之十一:刚需中的刚需——网站篡改监控
网站篡改事件近些年来越演越烈,其中包括政府、教育、金融、事业企业单位等。根据国家互联网应急响应中心发布的《2015年中国互联网网络安全报告》中指出,“2015年CNCERT/CC工检测到境内被篡改的网站数量为24550个,其中境内政府网站篡改数量为…

Http与RPC通信协议的比较
OSI网络结构的七层模型 各层的具体描述如下:第七层:应用层 定义了用于在网络中进行通信和数据传输的接口 - 用户程式;提供标准服务,比如虚拟终端、文件以及任务的传输 和处理; 第六层:表示层 掩…

基于 Python 和 OpenCV 构建智能停车系统
作者 | 努比来源 | 小白学视觉当今时代最令人头疼的事情就是找不到停车位,尤其是找20分钟还没有找到停车位。根据复杂性和效率的不同,任何问题都具有一个或多个解决方案。目前智能停车系统的解决方案,主要包括基于深度学习实现,以…

js获取鼠标位置
1.PageX/PageX:鼠标在页面上的位置,从页面左上角开始,即是以页面为参考点,不随滑动条移动而变化2.clientX/clientY:鼠标在页面上可视区域的位置,从浏览器可视区域左上角开始,即是以浏览器滑动条此刻的滑动到的位置为参考点,随滑动条移动 而变化. 可是悲剧的是,PageX只有FF…

Lua保留指定小数位数
默认会四舍五入 比如:%0.2f 会四舍五入后,保留小数点后2位print(string.format("%.1f",0.26)) ---会输出0.3,而不是0.2 Lua保留一位小数 --- nNum 源数字 --- n 小数位数 function Tool. GetPreciseDecimal(nNum, n)if type(nNum)…

htaccess文件用法收集整理
1.时区设置有些时候,当你在PHP里使用date或mktime函数时,由于时区的不同,它会显示出一些很奇怪的信息。下面是解决这个问题的方法之一。就是设置你的服务器的时区。你可以在这里找到所有支持的时区的清单。 1.SetEnv TZ Australia/Melbourne …

手把手教你使用 YOLOV5 训练目标检测模型
作者 | 肆十二来源 | CSDN博客这次要使用YOLOV5来训练一个口罩检测模型,比较契合当下的疫情,并且目标检测涉及到的知识点也比较多。先来看看我们要实现的效果,我们将会通过数据来训练一个口罩检测的模型,并用pyqt5进行封装&#x…

数据仓库数据模型之:极限存储--历史拉链表
摘要: 在数据仓库的数据模型设计过程中,经常会遇到文内所提到的这样的需求。而历史拉链表,既能满足对历史数据的需求,又能很大程度的节省存储资源。在数据仓库的数据模型设计过程中,经常会遇到这样的需求:1. 数据量比较…

super的用法(带了解)
super的用法(带了解) super的用法(带了解)posted on 2018-05-11 21:31 leolaosao 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/leolaosao/p/9026686.html

Posted content type isn't multipart/form-data
版权声明:欢迎转载,请注明沉默王二原创。 https://blog.csdn.net/qing_gee/article/details/48712507 在有文件上传的表单提交过程中,搞不好就会报Posted content type isnt multipart/form-data的错误。 解决办法 <form class"form-…

CSDN 十大技术主题盘点-AI篇
关于2021,我们能看到的技术变化有很多。当云原生向下而生,当分布式数据库席卷而至,当低代码平台扩展了开发的边界,当万物互联蔚然成风……我们看到了太多在2021年形成的变化,但也能看到这些趋势非但没有结束࿰…

PHP编程问题集锦
1. Win32下apache2用get方法传递中文参数会出错 test.php?a你好&b你也好传递参数是会导致一个内部错误解决办法:"test.php?a".urlencode(你好)."&b".urlencode(你也好)2. win32下的session不能正常工作 php.ini默认的session.save_path /tmp 这…

jsonp详解
json相信大家都用的多,jsonp我就一直没有机会用到,但也经常看到,只知道是“用来跨域的”,一直不知道具体是个什么东西。今天总算搞明白了。下面一步步来搞清楚jsonp是个什么玩意。 同源策略 首先基于安全的原因,浏览器…

PHP面向对象精华
PHP面向对象精华1 使用extends实现继承以及重载、魔术方法的含义 class B extends A 声明的时候B里可以没有A里的方法 调用的时候$bnew B(); $b->A里的方法(); $b->A里的属性1; $b->B里的方法(); $b->B里的方法(); 如果$anew A(); 可以 $a->A里的方法(); $a->…
springmvc和mybatis整合关键配置
springmvcmybaits的系统架构: 第一步:整合dao层 mybatis和spring整合,通过spring管理mapper接口。 使用mapper的扫描器自动扫描mapper接口在spring中进行注册。 第二步:整合service层 通过spring管理 service接口。 使用配置方式将…

阿里亲制明信片,字节、百度直接发锅……这些公司的新年礼盒越来越会玩~
整理 | 王晓曼出品 | 程序人生(ID:coder _life)每到年末,各大互联网大厂的新年礼盒都会作为…

html中radio、checkbox选中状态研究(静下心来看,静下心来总结)
html中radio、checkbox选中状态研究(静下心来看,静下心来总结) 一、总结 1、单选框的如果有多个checked 会以最后一个为准 2、js动态添加checked属性:不行:通过 $("[namesex]:eq(1)").attr("checked&q…

新年新气象,100 行 Python 代码制作动态鞭炮
作者 | FrigidWinter来源 | CSDN博客放鞭炮贺新春,在我国有两千多年历史。关于鞭炮的起源,有个有趣的传说。西方山中有焉,长尺余,一足,性不畏人。犯之令人寒热,名曰年惊惮,后人遂象其形…

php 反射类简介
反射是操纵面向对象范型中元模型的API,其功能十分强大,可帮助我们构建复 杂,可扩展的应用。其用途如:自动加载插件,自动生成文档,甚至可用来扩充 PHP 语言。php 反射api 由若干类组成,可帮助我们…

shell时间
Shell 调用系统时间变量 Linux常用命令获取今天时期:date %Y%m%d 或 date %F 或 $(date %y%m%d) 获取昨天时期:date -d yesterday %Y%m%d 获取前天日期:date -d -2day %Y%m%d 依次类推比如获取10天前的日期:date -d -10day %Y%m%d…

杨老师课堂_Java核心技术下之控制台模拟记事本案例...
预览效果图: 背景介绍: 编写一个模拟记事本的程序通过在控制台输入指令,实现在本地新建文件打开文件和修改文件等功能。 要求在程序中: 用户输入指令1代表“新建文件”,此时可以从控制台获取用户输入的文件内容&#x…

PHP的URL处理
完整URL地址: http://username:passwordhostname/path?argvalue#auchor 协议:http:// 用户名和密码: username:password 以:将两者分隔 主机名:hostname 和/为分隔符 路径: /path 以/开头、包含/符号 参…

UnitOfWork以及其在ABP中的应用
Unit Of Work(UoW)模式在企业应用架构中被广泛使用,它能够将Domain Model中对象状态的变化收集起来,并在适当的时候在同一数据库连接和事务处理上下文中一次性将对象的变更提交到数据中。 从字面上我们可以我们可以把UnitOfWork叫…

分享3个好用到爆的 Python 模块,点赞收藏
作者 | 俊欣来源 | 关于数据分析与可视化今天给大家介绍3个特别好用的Python模块,知道的人可能不多,但是特别的好用。PsutilPendulumPyfigletPsutilPython当中的Psutil模块是个跨平台库,它能够轻松获取系统运行的进程和系统利用率,…

使用XHProf分析PHP性能瓶颈(二)
上一篇文章里,我们介绍了如何基于xhprof扩展来分析PHP性能,并记录到日志里,最后使用xhprof扩展自带的UI在web里展示出来。本篇文章将讲述2个知识点: 使用xhgui代替xhprof的默认UI界面,更便于分析使用tideways扩展替换x…

PHP自动加载类—__autoload()和spl_autoload_register()
test.php <?phpfunction __autoload($class_name) {require_once $class_name . .php;}$obj new j();?> 当前目录下有j.php <?phpclass j{function __construct() {echo "成功加载";} }?> 正常输出:成功加载修改test.php代码<?phpf…