迹忆博客
当前位置: 主页 > 学无止境 > 编程语言 > 文章

ThinkPHP自动加载机制代码分析

发布时间: 2016-09-12 作者: 迹忆 浏览次数:

PHP的自动加载机制个人感觉使用起来还是很方便的。关于PHP的自动加载机制,其核心的方法是__autoload()和spl_autoload_register()两个函数。

在PHP5之后,当加载PHP类的时候,如果该类所在的文件没有被包含的话,Zend引擎会自动去调用__autoload()函数。当然,此函数必须由用户来实现。否则的话系统就会报错——找不到该类。

function __autoload($class_name) {   
         require_once ($class_name . “class.php”);   
}   
$memo= new Onmpw();

通过以上的例子,我们可以看出__autoload()在这个过程中做了三件事。第一、根据类名来确定类所在的文件名;第二、确定类文件所在的磁盘路径;第三、将类从磁盘文件中加载到系统中。其中最主要的还是第一和第二步。必须在开发中约定类名与磁盘文件的映射方法,只有这样我们的系统才能找到类名对应的磁盘文件从而将其加载到系统中。

但现在问题来了,假如在一个系统的实现中,假如需要使用很多其它的类库,这些类库可能是由不同的开发工程师开发,其类名与实际的磁盘文件的映射规则不尽相 同。这时假如要实现类库文件的自动加载,就必须在__autoload()函数中将所有的映射规则全部实现,因此__autoload()函数有可能会非常复杂,甚至无法实现。最后可能会导致__autoload()函数十分臃肿,这时即便能够实现,也会给将来的维护和系统效率带来很大的负面影响。还有一个问题就是__autoload()函数只能使用一次。在你的项目中使用了__autoload()函数,当你引用了其它的项目,在你引用的项目中也有一个__autoload()函数,这样两个函数就会有冲突。

因此,PHP5给我们提供了spl_autoload_register()函数来避免上述的问题。spl_autoload_register()函数允许我们自己定义自动加载函数。

PHP在实例化一个对象时(实际上在实现接口,使用类常数或类中的静态变量,调用类中的静态方法时都会如此),首先会在系统中查找该类(或接口)是否存在,如果不存在的话
尝试使用autoload机制来加载该类。而autoload机制的主要执行过程为:

(1) 检查执行器全局变量函数指针autoload_func是否为NULL。
(2) 如果autoload_func==NULL, 则查找系统中是否定义有__autoload()函数,如果没有,则报告错误并退出。
(3) 如果定义了__autoload()函数,则执行__autoload()尝试加载类,并返回加载结果。
(4) 如果autoload_func不为NULL,则直接执行autoload_func指针指向的函数用来加载类。

注意此时并不检查__autoload()函数是否定义。

也就是说:使用了spl_autoload_register()函数之后,当我们加载类的时候,系统首先会检查是否注册了自定义的自动加载函数。如果没有定义,系统会调用__autoload()函数(前提是我们事先了__autoload()函数)。如果自定义了自动加载函数,系统就会使用自定义的加载函数加载类。

function classLoader($class_name) {
    require_once ($class_name . “class.php”);   
}
spl_autoload_register('classLoader');
$obj = new Onmpw();

如果使用spl_autoload_register()函数注册了自动加载函数,但是没有找到我们需要实例化的类。此时,即使我们实现了__autoload()函数,该函数也不会执行。

好了,简单的介绍了spl_autoload_register()和__autoload()这两个函数以后,下面我们再简单分析一下ThinkPHP中的自动加载机制。

ThinkPHP的自动加载机制的实现是在Think.class.php中

static public function start() {
      // 注册AUTOLOAD方法
      spl_autoload_register('Think\Think::autoload');     
      // 设定错误和异常处理
      register_shutdown_function('Think\Think::fatalError');
           ……
}

我们看,在启动方法start()中使用了spl_autoload_register();方法注册了自定义的加载类库的函数。

在autoload()函数中,是检测是否有类和类文件的映射,如果有映射,那么直接导入文件即可。

if(isset(self::$_map[$class])) {
    include self::$_map[$class];
}

当然,如果没有映射,那就需要进行其他的分析。ThinkPHP类文件映射的注册函数如下

static public function addMap($class, $map=''){
    if(is_array($class)){
        self::$_map = array_merge(self::$_map, $class);
    }else{
        self::$_map[$class] = $map;
    }       
}

接下来就是查看是否使用了命名空间。如果使用了命名空间,查看其命名空间的有效性,否则的话就检测自定义的命名空间。最后如果二者都不是的话就以模块为命名空间

if(in_array($name,array('Think','Org','Behavior','Com','Vendor')) || is_dir(LIB_PATH.$name)){
      // Library目录下面的命名空间自动定位
      $path = LIB_PATH;
}else{
      // 检测自定义命名空间 否则就以模块为命名空间
      $namespace  =   C('AUTOLOAD_NAMESPACE');
      $path = isset($namespace[$name])? dirname($namespace[$name]).'/' : APP_PATH;
}

最后如果没有开启命名空间的使用,那加载还是比较简单的。首先是加载类库层,如果加载成功,那直接结束该函数的运行。

foreach(explode(',',C('APP_AUTOLOAD_LAYER')) as $layer){
    if(substr($class,-strlen($layer))==$layer){
       if(require_cache(MODULE_PATH.$layer.'/'.$class.EXT)) {
             return ;
       }
    }           
}

如果没有加载成功的话,那就根据设置的自动加载的路径进行尝试搜索加载。

foreach (explode(',',C('APP_AUTOLOAD_PATH')) as $path){
     if(import($path.'.'.$class))
        // 如果加载类成功则返回
        return ;
}

我们注意到,在上面的代码中有C('APP_AUTOLOAD_PATH')。该选项APP_AUTOLOAD_PATH是在配置文件中进行配置的

'APP_AUTOLOAD_PATH'  =>  '', //该项有效的前提是关闭APP_USE_NAMESPACE
'APP_USE_NAMESPACE'  =>  false,    // 应用类库是否使用命名空间

对于ThinkPHP的自动加载机制就介绍这么多。了解其内部的代码机制个人认为对使用ThinkPHP开发有些帮助。

希望本文对大家有所帮助。

赞助
迹忆博客

除非注明转载,本站文章均为原创,欢迎转载,转载请以链接形式注明出处

本文地址: