http://www.php.net/manual/zh/language.oop5.autoload.php

  1. 类库文件 本文档中所说的类库文件是指被包含(include/require)的公共文件,他们通常定义一些class, function

传统include/require的不足

在没用Autoloader的时候,怎样加载类库文件?最容易想到的是用include/require来包含类库文件,这种文件包含通常会有如下问题:

  1. 目录名和文件名变化引起程序代码变化
    当类库文件目录名或者文件名需要更改的时候,所有include了这个文件的php文件也要随着修改,这加大了源代码目录结构重构的负担。

    Windows和Linux对文件路径大小写和目录分隔符(斜线和反斜线)的处理不同,也使得PHP程序员需要花费相当一部分精力来应对文件名和文件路径问题。
  2. 相对路径的性能问题
    我们不会把类库文件的绝对路径写死在代码里,于是采用相对路径。

    一 种做法是设置php.ini和include_path值,然后给include()传入一个相对路径,Zend Framework和雅虎就是这样做的,这种方案存在显而易见的性能问题,include_path的值越多,性能损失就越大。php引擎处理 include_path的机制参见http://www.php.net/manual/en/ini.core.php#ini.include-path。包含文件时使用绝对路径也能让APC,eAccelerator等Opcode Cache更有效地缓存他们。

    另一种流行的方法是利用"FILE"魔术变量取得应用的根路径,include的时候使用基于“应用的根路径”的绝对路径,如include($appRoot . "conf/db.php"),这个方法很好的解决了相对路径带来的性能问题,CakePHP,Symfony等就是用的这种方案。
  3. 类库文件间相互依赖的问题
    类 库文件之间存在依赖,为了保证运行时不出现“类定义找不到”的情况,类库文件会用将需要的更基础的类库包含进来,又为了保证不重复包含,通常要用 include_once/require_once,Zend Framework就是这样做的。include_once比include慢。

当团队里不同水平,不同喜好的成员共同维护一份代码时,这些问题尤其严重。

Lotus Autoloader如何解决这些问题

为了解决上面这些问题,我在kiwiphp/lotusphp中加入了autoloader。Lotus Autoloader是这样解决上述问题的:

  1. Autoloader会动态扫描需要自动加载的类库目录,生成array("类名" => "文件")数组,用了Autoloader的程序,完全不需要写include/require来加载类库,代码里不会出现类库文件的路径、文件名。
  2. 由于代码里已经不需要写类库文件的路径了,自然也不存在相对路径的问题。
  3. php5的autoload()机制会解决这个依赖的问题,详细情况参见:http://php.net/manual/en/language.oop5.autoload.php

和其它Autoloader有什么不同

  1. Zend Framework和QeePHP的Autoloader机制是通过类名翻译出文件路径,而Lotus Autoloader是查找一个 array("类名" => "文件")数组,原因是我们(Lotusphp开发者)希望类名不要跟文件路径耦合,这样PHP程序员写程序时不必关注类的路径,只要名字写对了,就可以 加载,重构的时候调整了类文件的目录或者名字,也不影响调用他的程序。
    另一个好处是这种方案兼容性较好,Zend Framework的组件也能被Lotus Autoloader自动加载。而Zend Framework和Qee则希望基于他们建立的应用程序命名更规范。
    当然这种Autoload机制导致类库的class名字不能重复,在多年的实践中,我们发现,与“类名不要跟文件路径耦合”这个问题比起来,类名唯一引起的痛苦要小得多。

常见问题

  1. Q:类名是不是必须得唯一?如果重名怎么办?
    A:类名必须唯一,如果重名会被覆盖
  2. Q:Autoloader会不会把所有的class都加载进来?
    A:不会,只有在new HelloWorld的时候,才把HelloWorld所在的文件include进来,绝对的按需加载!
  3. Q:怎么在Lotus里没看到autoload()函数?
    A:Lotus Autoloader自动加载的函数实际是LtAutoloader->loadClass(),spl_autoload_register(array($this, "loadClass"));这个语句等于是告诉 php引擎:当你找autoload()的时候,就直接去找LtAutoloader->loadClass()吧.通过spl_autoload_register注册了自己的函数,PHP就不会调用autoload()函数,而会调用自定义的函数,也就是loadClass

鸣谢

  1. Autoload的理念来自Symfony,我是用Symfony做应用的时候享受Autoload的好处才在kiwiphp中加入Autoloader的。
  2. QeePHP的作者dualface等人让我知道了PHP5的autoload()机制,我才放弃了原本那个笨拙的办法:把所有类库打包成一个大文件,然后包含进来,我还为这个打包机制设计了黑白名单。详细讨论过程参见:http://www.phpchina.com/bbs/viewthread.php?tid=142775
  3. PHPChina的nightsailer提供的"class->file" mapping方案跟我最终采用的方案完全一样,刚开始我没看懂他的回复,等我自以为想到一个最合适的方案时,才发现之前nightsailer已经说过了:)