在這個例子中,no-autoload .php檔案需要使用Person類,它使用了require_once將其包含,然後就可以直接使用Person類別來實例化一個物件。 但隨著專案規模的不斷擴大,使用這種方式會帶來一些隱含的問題: 如果一個PHP檔案需要使用很多其它類,那麼就需要很多的require/include語 句,這樣有可能會造成遺漏或包含進不必要的類文件。 如果大量的文件都需要使用其它的類,那麼要保證每個文件都包含正確的類文件肯定是一個噩夢。 PHP5為這個問題提供了一個解決方案,這就是類別的自動裝載(autoload)機制。 autoload機制可以使得PHP程式有可能在使用類別時才自動包含類別文件,而不是一開始就將所有的類別文件include進來,這種機制也稱為lazy loading。 複製程式碼
通常PHP5在使用一個類別時,如果發現這個類別沒有加載,就會自動運行__autoload()函數,在這個函數中我們可以加載需要使用的類別。 在我們這個簡單的範例中,我們直接將類別名稱加上副檔名”.class.php」構成了類別檔案名,然後使用require_once將其載入。 從這個例子中,我們可以看出 autoload至少要做三件事情, 第一件事是根據類別名稱來決定類別檔案名稱; 第二件事是確定類別檔案所在的磁碟路徑(在我們的例子是最簡單的情況,類別與呼叫它們的 PHP程式檔案在同一個資料夾下); 第三件事是將類別從磁碟檔案載入到系統中。第三步最簡單,只需要使用include/require即可。 要實現第一 步,第二步的功能,必須在開發時約定類別名稱與磁碟檔案的對應方法,只有這樣我們才能根據類別名稱找到它對應的磁碟檔案。 因此,當有大量的類別檔案要包含的時候,我們只要確定對應的規則,然後在__autoload()函數中,將類別名稱與實際的磁碟檔案對應起來,就可以實現lazy loading的效果。 從這裡我們也可以看出__autoload()函數的實作中最重要的是類別名稱與實際的磁碟檔案映射規則的實作。 但現在問題來了,如果在一個系統的實作中,如果需要使用很多其它的類別庫,這些類別庫可能是由不同的開發人員編寫的,其類別名稱與實際的磁碟檔案的映射規則不盡相同。這時如果要實現類別庫檔案的自動加載,就必須在__autoload()函數中將所有的映射規則全部實現,這樣的話__autoload()函數有可能會非常複雜,甚至無法實現。最後可能會導致__autoload()函數十分臃腫,這時即便能夠實現,也會對將來的維護和系統效率帶來很大的負面影響。在這種情況下,難道就沒有更簡單、清楚的解決方法了吧?答案當然是:NO! 在看進一步的解決方法之前,我們先來看看PHP中的autoload機制是如何實現的。 二、 PHP的autoload機制的實作 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函數。 三、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函數了。 四、autoload效率問題及對策 使用autoload機制時,許多人的第一個反應就是使用autoload會降低系統效率,甚至有人乾脆提議為了效率不要使用autoload。 在我們了解了autoload實現的原理後,我們知道autoload機製本身並不是影響系統效率的原因,甚至它還有可能提高系統效率,因為它不會將不需要的類別載入到系統中。 那為什麼很多人都有一個使用autoload會降低系統效率的印象呢? 實際上,影響autoload機製效率本身正是使用者設計的自動載入函數。 如果它不能高效的將類名與實際的磁碟文件(注意,這裡指實際的磁碟文件,而不僅僅是文件名)對應起來,系統將不得不做大量的文件是否存在(需要在每個include path包含的路徑中去尋找)的判斷,而判斷檔案是否存在需要做磁碟I/O操作,眾所周知磁碟I/O操作的效率很低,因此這才是使得autoload機制效率降低的罪魁禍首! 因此,當我們在系統設計時,我們需要定義一套清晰的將類別名稱與實際磁碟檔案對應的機制。這個規則越簡單越明確,autoload機制的效率就越高。 結論:autoload機制並不是天然的效率低下,只有濫用autoload,設計不好的自動裝載函數才會導致其效率的降低。 |