对PHP加载和spl_autoload自动加载机制的深入了解
(1)对加载机制概述
当我们使用PHP的面向对象模式开发的系统,我们在一个单独的文件通常使用每个类的实现,这使得它易于重用的类,并在同一时间,便于日后的维护,这也是面向对象设计的基本思想。在PHP5,如果你需要使用一个类,你只需要把它直接与包括 / require.the以下是一个实际的例子:
复制代码代码如下所示:
person.class.php / * * /
< PHP
类人{
$名称,$年龄;
功能__construct($name,$AGE)
{
这个名字;
美元->年龄= $年龄;
}
}
>
no_autoload.php / * * /
< PHP
require_once(人。类。PHP);
为人=人(牛郎星,6);
var_dump(为人);
>
在这种情况下,该no-autoload.php文件需要使用Person类,它使用require_once包含它,然后它可以直接使用Person类实例化一个对象。
但随着项目规模的不断扩大,这种方法的使用会带来一些隐藏的问题:如果一个PHP文件需要许多其他类的使用,所以我们需要很多需要 /包括报表,这可能会导致丢失或包含不必要的文件。如果有大量的文件需要使用其他类,它是确保每个文件包含正确的类文件的噩梦。
PHP5提供了一个解决这个问题的,这是类的加载机制。加载机制使得PHP程序自动包括类文件时使用的类,而不是进入所有的类文件包括在第一,这也被称为延迟加载。
以下是使用加载机构加载Person类的例子。
复制代码代码如下所示:
autoload.php / * * /
< PHP
功能__autoload($ className){
require_once($类名。类);
}
为人=人(牛郎星,6);
var_dump(为人);
>
通常当PHP5是类的使用,如果发现类不加载,它会自动运行__autoload()函数。在这个函数中,我们可以加载需要使用的类。在我们的简单例子,我们直接添加类名称和扩展名title=延伸的延伸。class.php形成类文件的名称,然后使用require_once加载它。从这个例子,我们可以看到,加载至少要做三件事,第一件事是确定文件的名称,其次是确定磁盘路径里的文件类型(在我们的例子中是最简单的情况下,PHP程序文件在同一文件夹中的第三件事是打电话给他们),从磁盘文件被加载到系统中。第三步是最简单的,只使用包括 / require.to实现第二步第一步的功能,我们必须对映射的同意在开发时类名和磁盘文件之间的方法。只有这样,我们才能根据类名找到相应的磁盘文件。
因此,当有大量的类文件被包括在内,只要我们确定相应的规则,然后对应的__autoload实际磁盘文件()函数,我们可以实现延迟加载的效果。从这里我们也可以看到,在__autoload实施最重要事()函数是类名称的实施和实际磁盘文件的映射规则。
但现在问题来了。如果我们需要在一个系统的实现使用许多其他的类库,这些类库可能由不同的开发人员编写的,和他们的类名称和实际磁盘文件的映射规则是不同的。如果我们想实现类的自动加载库文件,我们必须在__autoload实施所有的映射规则()的功能,使__autoload()函数可以很复杂甚至是不可能实现的。最后,它可能会导致__autoload()函数很臃肿。此时,即使可以实现,也会对未来的维护和系统效率带来很大的负面影响,在这种情况下,难道没有更简单、更明确的解决方案吗答案当然是否定的!以前我们看进一步的解决方案,让我们来看看如何在PHP中的加载机制实施。
(2)对PHP的加载机制的实现
我们知道,PHP文件的实施分为两个独立的过程,第一步是编译PHP文件为普通称为操作码字节码(实际上是编译成字节数组zend_op_array),第二步是由虚拟机执行的操作码。所有PHP的行为是由这些操作码的实现。因此,为了研究加载在PHP中的实现机制,我们autoload.php文件编译成操作码,然后研究什么PHP的过程中做基于这些操作码。
* autoload.php列出操作码是利用发达opdump工具
* /
复制代码代码如下所示:
< PHP
/ / require_once(人。PHP);
功能__autoload($ className){
0 NOP
0:1
如果(!class_exists($ className)){
1 send_var!零
2 do_fcallclass_exists{ 1 } extval:
3 bool_not 0美元= > { 1 } ~ RES
4 jmpz ~ 1 -> 8
require_once($名..班。PHP);
5连接!0,类php = { 2 }
6 include_or_eval ~ 2,require_once
}
7 JMP -> 8
}
8返回null
P =新的人('fred ',35);
1 fetch_class '人' = > RES { 0 }:
2新:0 = { $ 1 }
3 send_val '弗莱德'
4 send_val 35
5 do_fcall_by_name { 2 } extval:
6分配!0,1美元
var_dump(P);
7 send_var!零
8 do_fcallvar_dump{ 1 } extval:
>
在autoload.php第十线,我们需要实例化的类人的对象。因此,自动加载机制必将体现在编译码的线,从上面的第十行代码生成的码,我们知道当对象实例化的人,fetch_class指令首先执行。我们开始我们的探索从fetch_class说明PHP的处理过程。
通过查看PHP源代码(我使用PHP 5.3alpha2版),下面的调用序列可以发现:
zend_vm_handler(109,zend_fetch_class,hellip;)(zend_vm_def。H 1864线)
= > zend_fetch_class(zend_execute_api。C 1434)
= > zend_lookup_class_ex(964行zend_execute_api。C)
= > zend_call_function(fcall_info,fcall_cache)(zend_execute_api。C 1040)
在调用最后一步之前,让我们先看看调用的关键参数:
设置变量的值是__autoload autoload_function / * * /
fcall_info.function_name = autoload_function; / /啊,终于找到__autoload
hellip;
fcall_cache.function_handler =如(autoload_func); / / autoload_func!
zend_call_function是在Zend引擎最重要的功能。它的主要功能是在PHP程序或库PHP itself.zend_call_function函数有两个重要的指针参数fcall_info和fcall_cache执行用户定义的函数,两个重要的结构点,一个是zend_fcall_info和其他zend_fcall_info_cache.the主要工作如下:如果zend_call_function fcall_cache.function_handler指针为空,然后找到函数称为fcall_info.function_name功能,如果它存在,是执行;如果fcall_cache.function_handler不是空的,是fcall_cache.function_handler举行直接的功能。
现在我们知道PHP在对象的实例化(事实上在接口,静态变量,实现使用常数或类的静态方法调用的类都是如此,会发现第一类)的系统(或接口)的存在,如果没有如果你尝试使用加载的类加载机制。的加载机制的主要执行过程如下:
(1)检查全局变量的函数的指针autoload_func的执行者是空的。
(2)如果autoload_func = = null,这__autoload()函数在查找系统定义的,如果没有,错误报道和退出。
(3)如果__autoload()函数的定义,对__autoload执行()尝试加载类和返回结果的负荷。
(4)如果autoload_func是无效的,由autoload_func指针的函数来加载该类。注意,__autoload()函数在这一点上是不确定。
真理最后,PHP提供了实现自动加载机制的两种方法,这是我们前面所提到的,是用户定义的__autoload使用()函数,它通常是在PHP源码程序实现;另一种是设计一个函数,该autoload_func指向它的指针,这是常用的C语言的扩展如果__autoload()函数实现的autoload_func实现(的autoload_func涉及PHP函数),那么只有autoload_func函数的执行。
(3)SPL加载机制的实现
SPL是标准的PHP库的缩写(标准PHP库),它是一个扩展库介绍了PHP5,其主要功能包括自动加载机制和各种迭代器接口或类列入实施。SPL的加载机制的实现是通过与自动加载功能,点的函数指针autoload_func自己的implementation.spl功能实现了两者的不同功能,spl_autoload和spl_autoload_call,实施不同的自动加载机制autoload_func指出这两种不同的函数地址。
spl_autoload是SPL执行默认的自动加载功能,它的功能很简单,它可以接收两个参数,第一个参数是class_name美元,这类的名字,第二个参数file_extensions美元是可选的,表示的类文件扩展名title=延伸>扩展,你可以指定多个延伸title=延伸>扩展file_extensions美元,保护展览名称用分号;如果没有指定,将使用默认扩展名title=延伸>扩展。公司or.php.spl_autoload第一圈class_name美元为小写,然后寻找美元所有的class_name.inc美元class_name.php文件包含路径(如果没有file_extensions美元参数指定),如果发现,它加载的类文件,你可以手动使用spl_autoload(人。类。PHP)加载的人类。事实上,它类似于要求 /包括,它可以指定一些扩展title=扩展>扩展。
如何使spl_autoload自动工作,即点autoload_func到spl_autoload答案是使用spl_autoload_register功能。当第一个打电话给spl_autoload_register()在一个PHP脚本不使用任何参数,你可以点到spl_autoload autoload_func。
我们知道,spl_autoload具有简单的功能上面的描述,它是在SPL扩展实现,我们不能扩展它的功能。如果你想实现你自己的更灵活的自动加载机制在这一点上,spl_autoload_call闪烁的功能。
让我们在对spl_autoload_call.in SPL模块的实现的奇迹,一看,一个全局变量autoload_functions,它本质上是一个哈希表,但我们可以简单地视为一个链接列表,列表中的每个元素是一个函数指针指向一个具有自动加载功能,spl_autoload_call实现本身很简单,只是为了在列表中执行每个函数,每个函数执行后,法官需要类完成已经加载,如果加载成功直接返回,不再继续在列表中执行其他功能。如果此列表中的所有的功能没有被加载后,所有的功能都完成了,spl_autoload_call将退出直接和不报告E误差的用户。因此,对加载机制的使用并不能保证类可以自动自动加载。关键是看你的自动加载功能是如何实现的。
所以谁会自动加载功能链autoload_functions维持这是前面提到的spl_autoload_register功能。它可以登记用户定义的自动加载功能的链接列表,点autoload_func函数指针spl_autoload_call功能。我们也可以从autoload_functions列表通过spl_autoload_unregister函数删除注册功能。
最后一段说,当autoload_func指针不是空的,它不会自动执行__autoload()函数。现在autoload_func指出spl_autoload_call。如果我们想__autoload()函数来工作,我们应该做些什么呢当然,使用spl_autoload_register(__autoload)电话登记,它在autoload_functions列表。
现在,回到第一季度的最后一个问题,我们的解决方案:我们实现了各自的自动加载功能根据不同的命名机制,每个类库,然后登记为SPL自动加载功能队列spl_autoload_register.so我们没有保持一个非常复杂的__autoload功能。
(4)加载效率存在的问题及对策
使用自动加载机制的时候,很多人的第一反应是使用自动降低系统效率。有些人甚至认为,加载不应该用于效率。了解后加载的实现原理,我们知道加载机制本身并不影响系统效率的原因,甚至可以提高系统的效率,因为它将不加载不必要的类到系统。
那么为什么许多人有一种印象,使用加载会降低系统的效率事实上,在加载机制本身的效率是一个自动加载功能的用户设计的。如果不是有效的磁盘文件的类名称和实际(注意,这里指的是实际的磁盘文件,而不是文件名)匹配系统将不得不做了很多的文件存在(的路径必须包含在每个包含路径中找到)的判断,而判断一个文件是否存在做我/ O磁盘操作的效率,大家都知道我 / O操作盘上是很低的,所以这是降低加载效率机制的罪魁祸首!
因此,我们在系统设计时,我们需要为类的名称映射到实际的磁盘文件定义了一个明确的机制,简单和清晰的规则,对加载机制效率更高。
结论:自动加载机制是不自然的低效率,只有加载的滥用,可怜的自动加载功能的设计会导致其效率的降低。