自动加载机制的主要执行过程:
(1) 检查执行器全局变量函数指针autoload_func是否为NULL。
(2) 如果autoload_func==NULL, 则查找系统中是否定义有__autoload()函数,如果没有,则报告错误并退出。
(3) 如果定义了__autoload()函数,则执行__autoload()尝试加载类,并返回加载结果。
(4) 如果autoload_func不为NULL,则直接执行autoload_func指针指向的函数用来加载类。注意此时并不检查__autoload()函数是否定义。
根据以上定义,自动加载机制在实现的时候分为两大类:__autoload,SPL autoload
(1)最简单的文件引入方式require/include:
在使用PHP的OO模式开发系统时,通常大家习惯上将每个类的实现都存放在一个单独的文件里,这样会很容易实现对类进行复用,同时将来维护时也很便利。在一个脚本文件中我们需要使用到其他文件中的类时,最简单的做法就是使用require/include将类文件引入。但时当我们需要用到大量的类时,就不得不写很多的require/include语句,同时也可能会造成个别类文件的引入遗漏了。
PHP5为这个问题提供了一个解决方案,这就是类的自动加载机制。autoload机制可以使得PHP程序有可能在使用类时才自动包含类文件,而不是一开始就将所有的类文件include进来,这种机制也称为lazy loading。
(2)__autoload
void __autoload ( string$class
) 你可以通过定义这个函数来启用类的自动加载。
<?php
/*下面是使用autoload机制加载Dog类的例子:*/根据注册的先后,如果在第一个注册的方法或函数里加载了类文件,就不会再执行第二个被注册的类的方法或函数。反之就会执行第二个被注册的类的方法或函数。
function __autoload($classname){
$s=$classname.'class.php';
require_once('./'.$s);
}
$d=new Dog('哈巴狗',20);
?>
从这个例子中,我们可以看出 __autoload至少要做三件事情,第一件事是根据类名确定类文件名,第二件事是确定类文件所在的磁盘路径(在我们的例子是最简单的情况,类与调用它们的 PHP程序文件在同一个文件夹下),第三件事是将类从磁盘文件中加载到系统中。第三步最简单,只需要使用include/require即可。要实现第一 步,第二步的功能,必须在开发时约定类名与磁盘文件的映射方法,只有这样我们才能根据类名找到它对应的磁盘文件。那么这表明:__autoload()函数的实现中最重要的是类名与实际的磁盘文件映射规则的实现。
那么问题就来了,如果有大量开发人员参与一个项目类库的开发,或者需要引入第三方类库,这会导致类名与实际的磁盘文件映射规则有很多种,那么__autoload函数为了实现类文件的自动加载,就需要一一实现这些规则,并且还要进行大量的if-eles判断,这造成了__autoload函数的臃肿,效率低下,维护也造成不便。
(3)spl_autoload,spl_autoload_call (手动调用)
SPL autoload机制能够很好的解决上述问题。 SPL autoload机制的实现是通过将函数指针autoload_func指向自己实现的具有自动装载功能的函数来实现的。SPL有两个不同的函数 spl_autoload, spl_autoload_call,通过将autoload_func指向这两个不同的函数地址来实现不同的自动加载机制。
1. spl_autoload:(SPL实现的默认的自动加载函数)
void spl_autoload ( string$class_name
[,string$file_extensions
] )
它可以接收两个参数,第一个参数是$class_name,表示类名,第二个参 数$file_extensions是可选的,表示类文件的扩展名,多个扩展名用逗号隔开
在默认情况下,本函数先将类名转换成小写,再在小写的类名后加上 .inc 或 .php 的扩展名作为文件名,然后在所有的包含路径(include paths)中检查是否存在该文件。
常见的用法是和set_include_path,sql_autoload_extensions搭配使用
/*例子如下:MVC中实现控制器类的载入*/
public function load_controller($controller)
{
if ($controller) {
set_include_path($this->controllerDirectoryPath);
spl_autoload_extensions('.php');
spl_autoload($class);
}
}
2. spl_autoload_call
如果需要多条 autoload 函数,spl_autoload_register() 满足了此类需求。 它实际上创建了 autoload 函数的队列,按定义时的顺序逐个执行。相比之下, __autoload()只可以定义一次。
void spl_autoload_call ( string$class_name
)
可以直接在程序中手动调用此函数来使用所有已注册的__autoload函数装载类或接口。
在使用spl_aulotoload_call后,系统会根据autoload函数队列中函数注册的先后,如果在第一个注册的方法或函数里加载了类文件,就不会再执行第二个被注册的类的方法或函数。反之就会执行第二个被注册的类的方法或函数。
注意:如果在你的程序中已经实现了__autoload()函数,它必须显式注册到__autoload()队列中。因为 spl_autoload_register()函数会将Zend Engine中的__autoload()函数取代为spl_autoload()或spl_autoload_call()。
(4) autoload效率问题及对策
使 用autoload机制时,很多人的第一反应就是使用autoload会降低系统效率,甚至有人干脆提议为了效率不要使用autoload。在我们了解了 autoload实现的原理后,我们知道autoload机制本身并不是影响系统效率的原因,甚至它还有可能提高系统效率,因为它不会将不需要的类加载到 系统中。
那么为什么很多人都有一个使用autoload会降低系统效率的印象呢?实际上,影响autoload机制效率本身恰恰是用户设计的自动加载函数。如果它 不能高效的将类名与实际的磁盘文件(注意,这里指实际的磁盘文件,而不仅仅是文件名)对应起来,系统将不得不做大量的文件是否存在(需要在每个 include path中包含的路径中去寻找)的判断,而判断文件是否存在需要做磁盘I/O操作,众所周知磁盘I/O操作的效率很低,因此这才是使得autoload机 制效率降低的罪魁祸首!
因此,我们在系统设计时,需要定义一套清晰的将类名与实际磁盘文件映射的机制。这个规则越简单越明确,autoload机制的效率就越高。