在使用面向對象方法做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 。
緩存類裝載,當成功裝載一個類所在的文件后,可以將類名與文件名記錄下來以便下次使用,這樣就無需每次都進行循環遍歷,在某些情況下可以節省查找時間。