在使用面向对象方法做PHP开发时,可能会经常使用到各个路径中的类文件,这就需要大量的 include 或 require,而 PHP 提供了一个比较快捷的方式,就是利用函数 __autoload 可以编程实现动态的类装载功能,这样就不需要手动的编写大量include 或 require,好了,下面切入正题。
设计思路:如果想实现自动类装载功能,就必须使用 PHP 提供的 __autoload 函数,该函数只有一个参数,即我们在程序编写时所涉及到的类名称,当函数被调用时,我们要做的就是利用传入的类名加载这个类所在的文件。
第一个问题就是,我们如何得知类属于哪个文件名呢?在做 Java 或 .Net 程序时,整个运行的程序会根据类名在内存中查找对应的类型信息(常常会伴随着命名空间作为限定),内存的类型信息来自于应用程序初始化时的类文件装载,这点与 PHP 是有区别的,PHP 程序不会装载所有内容,它只是在代码运行到某处需要装载必要的文件时才会发出装载请求。暂时抛弃何时装载这个问题,再次回到装载类文件,不管是 .Net 还是 Java,它们在装载类型信息的时候,都是类名查找类型信息的,从这点看来 __autoload 采用的也是相同的方法,但是 PHP 在定义类时并不要求文件名与类名保持一致,这就有可能造成文件与类杂乱无章,给类装载实现带来麻烦,所以有必要人为的规定类定义与其所在的文件需要采用相同的名称,或者两者之间按照某种规则可以互相映射,这样就很容易。
第二个问题,类所在的文件名已经可以确定,但是这个文件是属于哪个目录呢?Java 可以根据包名来进行查找,.Net 有命名空间,虽然新版本的 PHP 引入了命名空间的概念,但是既存的服务器也许会因为多种原因不能为每个客户提供最新的环境,所以还是得从 PHP 本身下手比较实用。虽然没有命名空间,但是可以借鉴操作系统的环境变量概念,将不同的路径名放入环境变量中,这样就可以从环境变量中读取各个目录,然后找到目标类所在的文件。
一、类名与文件名映射
这一步要做的就是定义文件名与类名映射规则,类名采用驼峰命名法,即类名的每个单词首字母需大写,而文件的命名则采用全部单词小写,单词之间以下划线分割,后缀名为 .class.php 。
二、在环境变量中进行路径遍历
仿照 UNIX 或 Windows 的环境变量的定义方式,将多个文件夹以分号或冒号分隔,罗列在 CLASSPATH 中。当程序读取时,可以将文件夹路径放入数组中。
三、开始装载
调用函数 require 或 include 并利用组合好的文件路径进行文件装载,但是有两处需要注意,首先需要判断组合好的路径是否有效,其次,文件成功装载后,为了效率问题,可以马上退出 __autoload 函数。
define(CLASSPATH, dirname(__FILE__).'/entity'.':'.dirname(__FILE__).'/meta');function __autoload($classname) {
$filename = strtolower(preg_replace('/(?<=/B)([A-Z])/s', '_$1', $classname)) . ".class.php";
foreach (preg_split('/:/',CLASSPATH) as $cp) {
if (file_exists("$cp/$filename")) {
require_once ("$cp/$filename");
break;
}
}
}
四、启用自动类装载功能
主动式:将该函数直接或间接包含在当前文件中,之后无论在文件何处编写代码,类文件都可以自动装载。
被动式:将该函数直接或间接包含在当前文件中,以当前文件为主控制程序,然后调用其它业务实现,这样在其他业务实现文件中就无需考虑类装载的问题了。
在图中采用的是主动式,question_parser.php 通过创建 meta 中存放的类,这些类又调用 entity 中的内容,这个过程仅仅在 question_parser.php 包含了定义 __autoload 的 question_sysext.php,关系图如下:
question_parser.php -> question_sysext.php
||
//
meta* => entity*
五、扩展思考
如果文件名与类名无任何关联性的话,可以装载 CLASSPATH 中定义的文件夹中所有 *.php 文件或是像例子那样装载 *.class.php 。
缓存类装载,当成功装载一个类所在的文件后,可以将类名与文件名记录下来以便下次使用,这样就无需每次都进行循环遍历,在某些情况下可以节省查找时间。