`
zccst
  • 浏览: 3291313 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

PHP autoload机制详解

 
阅读更多
zccst转载

批注:这篇文章应该算经典了,因为一搜一大堆。时隔多日再读的感受,有那么一点点入门。但仅凭这一篇文章去了解autoloader机制,那就要失望了。

(1) autoload机制概述

  在使用 PHP的OO模式开发系统时,通常大家习惯上将每个类的实现都存放在一个单独的文件里,这样会很轻易实现对类进行复用,同时将来维护时也很便利。这也是OO设计的基本思想之一。在PHP5之前,假如需要使用一个类,只需要直接使用include/require将其包含进来即可。下面是一个实际的例子:
class Person {
  var $name, $age;
  function __construct ($name, $age)
  {
      $this->name = $name;
      $this->age = $age;
  }
}

  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类的例子:
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在这过程中都做了些什么:

// require_once (”Person.php”);
function __autoload ($classname) {
      0 NOP
      0 RECV 1
    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
  require_once ($classname. “.class.php”);
      5 CONCAT !0, ‘.class.php’ =>RES[~2]
      6 INCLUDE_OR_EVAL ~2, REQUIRE_ONCE
   }
      7 JMP ->8
}
      8 RETURN null

$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

var_dump ($p);
  7 SEND_VAR !0
  8 DO_FCALL ‘var_dump’ [extval:1]



  在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行)

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

  fcall_info.function_name = &autoload_functi on; // 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,设计不好的自动装载函数才会导致其效率的降低。
分享到:
评论

相关推荐

    PHP autoload 机制详解

    本文主要介绍 PHP autoload 机制,这里整理了详细的知识资料供大家学习参考,希望能帮助有需要的小伙伴

    PHP autoload与spl_autoload自动加载机制的深入理解

    PHP autoload机制详解(1) autoload机制概述在使用PHP的OO模式开发系统时,通常大家习惯上将每个类的实现都存放在一个单独的文件里,这样会很容易实现对类进行复用,同时将来维护时也很便利。这 也是OO设计的基本思想...

    autoload机制

    autoload机制autoload机制

    深入PHP autoload机制的详解

    本篇文章是对PHP中的autoload机制进行了详细的分析介绍,需要的朋友参考下

    php __autoload自动加载功能详解

    php __autoload 自动加载功能的实现,简单而有效的让你认识这个函数,另外详解了如果去快速找到在一堆字母中找到大写字母的位置。

    PHP的autoload机制的实现解析

    在使用PHP的OO模式开发系统时,通常大家习惯上将每个类的实现都存放在一个单独的文件里,这样会很容易实现对类进行复用,同时将来维护时也很便利

    php自动加载autoload机制示例分享

    复制代码 代码如下:liuyuan@ebuinfo:/var/www/phpgcs/php_autoload$ ll ./*-rw-rw-r– 1 liuyuan liuyuan 800 Feb 19 11:39 ./func_autoload.php-rw-rw-r– 1 liuyuan liuyuan 906 Feb 19 11:28 ./spl_autoload....

    Laravel实现autoload方法详解

    function autoload($path){ require_once($path); } 一般每次导入的PHP文件不只一两个,会有很多,所以要定义一个数组来存储“类和PHP文件的映射关系”,就像这样: $classmap=[ 'DB'=>'api.php', '...

    php中spl_autoload详解

    SPL有两个不同的函数 spl_autoload, spl_autoload_call,通过将autoload_func指向这两个不同的函数地址来实现不同的自动加载机制。 spl_autoload 是SPL实现的默认的自动加载函数,它的功能比较简单。它可以接收两个...

    PHP autoload使用方法及步骤详解

    而对于PHP5来说也可以使用在PHP4里面的方式,将类文件包进来,但当有了__autoload之后,这种情况就可以靠编写规则也实现。 以下代码: <?php $obj_A = new clsA(); $obj_B = new clsB(); ?> PHP4下...

    PHP中spl_autoload_register()函数用法实例详解_.docx

    PHP中spl_autoload_register()函数用法实例详解_.docx

    spl_autoload_register与autoload的区别详解

    spl_autoload_register(PHP 5 >= 5.1.2)spl_autoload_register — 注册__autoload()函数说明bool spl_autoload_register ([ callback $autoload_function ] )将函数注册到SPL __autoload函数栈中。如果该栈中的函数...

    Laravel访问出错提示:`Warning: require(/vendor/autoload.php): failed to open stream: No such file or di解决方法

    本文实例讲述了Laravel框架访问...`Warning: require(/http/www.mywakavLee.cn/bootstrap/../vendor/autoload.php): failed to open stream: No such file or directory in /http/www.mywakavLee.cn/bootstrap/autolo

    PHP之autoload运行机制实例分析

    本文较为深入的分析了PHP的autoload运行机制。对于深入理解PHP运行原理有一定的帮助作用。具体分析如下: php实现autoload有两种方法: 1、拦截器__autoload() 2、设置全局变量函数指针autoload_func为指定函数。...

Global site tag (gtag.js) - Google Analytics