摘要:但現(xiàn)在問題來了,如果在一個系統(tǒng)的實現(xiàn)中,如果需要使用很多其它的類庫,這些類庫可能是由不同的開發(fā)人員編寫的,其類名與實際的磁盤文件的映射規(guī)則不盡相同。
1. autoload 機制概述PHP在魔術函數(shù)__autoload()方法出現(xiàn)以前,如果你要在一個程序文件中實例化100個對象,那么你必須用include或者require包含進來100個類文件,或者你把這100個類定義在同一個類文件中——相信這個文件一定會非常大。但是__autoload()方法出來了,以后就不必為此大傷腦筋了,這個類會在你實例化對象之前自動加載制定的文件。
在使用PHP的OO模式開發(fā)系統(tǒng)時,通常大家習慣上將每個類的實現(xiàn)都存放在一個多帶帶的文件里,這樣會很容易實現(xiàn)對類進行復用,同時將來維護時也很便利。這也是OO設計的基本思想之一。在PHP5之前,如果需要使用一個類,只需要直接使用include/require將其包含進來即可。下面是一個實際的例子:
/* Person.class.php */ name = $name; $this->age = $age; } } ?> /* no_autoload.php */
在這個例子中,no-autoload.php文件需要使用Person類,它使用了require_once將其包含,然后就可以直接使用Person類來實例化一個對象。
但隨著項目規(guī)模的不斷擴大,使用這種方式會帶來一些隱含的問題:如果一個PHP文件需要使用很多其它類,那么就需要很多的require/include語句,這樣有可能會造成遺漏或者包含進不必要的類文件。如果大量的文件都需要使用其它的類,那么要保證每個文件都包含正確的類文件肯定是一個噩夢。
PHP5為這個問題提供了一個解決方案,這就是類的自動裝載(autoload)機制。autoload機制可以使得PHP程序有可能在使用類時才自動包含類文件,而不是一開始就將所有的類文件include進來,這種機制也稱為lazy loading。
下面是使用autoload機制加載Person類的例子:
/* autoload.php */
通常PHP5在使用一個類時,如果發(fā)現(xiàn)這個類沒有加載,就會自動運行__autoload()函數(shù),在這個函數(shù)中我們可以加載需要使用的類。在我們這個簡單的例子中,我們直接將類名加上擴展名”.class.php”構成了類文件名,然后使用require_once將其加載。從這個例子中,我們可以看出autoload至少要做三件事情,第一件事是根據(jù)類名確定類文件名,第二件事是確定類文件所在的磁盤路徑(在我們的例子是最簡單的情況,類與調用它們的PHP程序文件在同一個文件夾下),第三件事是將類從磁盤文件中加載到系統(tǒng)中。第三步最簡單,只需要使用include/require即可。要實現(xiàn)第一步,第二步的功能,必須在開發(fā)時約定類名與磁盤文件的映射方法,只有這樣我們才能根據(jù)類名找到它對應的磁盤文件。
因此,當有大量的類文件要包含的時候,我們只要確定相應的規(guī)則,然后在__autoload()函數(shù)中,將類名與實際的磁盤文件對應起來,就可以實現(xiàn)lazy loading的效果。從這里我們也可以看出__autoload()函數(shù)的實現(xiàn)中最重要的是類名與實際的磁盤文件映射規(guī)則的實現(xiàn)。
但現(xiàn)在問題來了,如果在一個系統(tǒng)的實現(xiàn)中,如果需要使用很多其它的類庫,這些類庫可能是由不同的開發(fā)人員編寫的,其類名與實際的磁盤文件的映射規(guī)則不盡相同。這時如果要實現(xiàn)類庫文件的自動加載,就必須在__autoload()函數(shù)中將所有的映射規(guī)則全部實現(xiàn),這樣的話__autoload()函數(shù)有可能會非常復雜,甚至無法實現(xiàn)。最后可能會導致__autoload()函數(shù)十分臃腫,這時即便能夠實現(xiàn),也會給將來的維護和系統(tǒng)效率帶來很大的負面影響。在這種情況下,難道就沒有更簡單清晰的解決辦法了吧?答案當然是:NO! 在看進一步的解決方法之前,我們先來看一下PHP中的autoload機制是如何實現(xiàn)的。
2. PHP 的 autoload 機制的實現(xiàn)我們知道,PHP文件的執(zhí)行分為兩個獨立的過程,第一步是將PHP文件編譯成普通稱之為OPCODE的字節(jié)碼序列(實際上是編譯成一個叫做zend_op_array的字節(jié)數(shù)組),第二步是由一個虛擬機來執(zhí)行這些OPCODE。PHP的所有行為都是由這些OPCODE來實現(xiàn)的。因此,為了研究PHP中autoload的實現(xiàn)機制,我們將autoload.php文件編譯成opcode,然后根據(jù)這些OPCODE來研究PHP在這過程中都做了些什么:
/* autoload.php 編譯后的OPCODE列表,是使用作者開發(fā)的OPDUMP工具 * 生成的結果,可以到網(wǎng)站 http://www.phpinternals.com/ 下載該軟件。 */ 1: RES[~1] 4 JMPZ ~1, ->8 6: require_once ($classname. “.class.php”); 5 CONCAT !0, ‘.class.php’ =>RES[~2] 6 INCLUDE_OR_EVAL ~2, REQUIRE_ONCE 7: } 7 JMP ->8 8: } 8 RETURN null 9: 10: $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 11: 12: var_dump ($p); 7 SEND_VAR !0 8 DO_FCALL ‘var_dump’ [extval:1] 13: ?>
在autoload.php的第10行代碼中我們需要為類Person實例化一個對象。因此autoload機制一定會在該行編譯后的opcode中有所體現(xiàn)。從上面的第10行代碼生成的OPCODE中我們知道,在實例化對象Person時,首先要執(zhí)行FETCH_CLASS指令。我們就從PHP對FETCH_CLASS指令的處理過程開始我們的探索之旅。
通過查閱PHP的源代碼(我使用的是PHP 5.3alpha2版本)可以發(fā)現(xiàn)如下的調用序列:
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行)
在最后一步的調用之前,我們先看一下調用時的關鍵參數(shù):
/* 設置autoload_function變量值為”__autoload” */ fcall_info.function_name = &autoload_function; // Ooops, 終于發(fā)現(xiàn)”__autoload”了 … fcall_cache.function_handler = EG(autoload_func); // autoload_func !
zend_call_function是Zend Engine中最重要的函數(shù)之一,其主要功能是執(zhí)行用戶在PHP程序中自定義的函數(shù)或者PHP本身的庫函數(shù)。zend_call_function有兩個重要的指針形參數(shù)fcall_info, fcall_cache,它們分別指向兩個重要的結構,一個是zend_fcall_info, 另一個是zend_fcall_info_cache。zend_call_function主要工作流程如下:如果fcall_cache.function_handler指針為NULL,則嘗試查找函數(shù)名為fcall_info.function_name的函數(shù),如果存在的話,則執(zhí)行之;如果fcall_cache.function_handler不為NULL,則直接執(zhí)行fcall_cache.function_handler指向的函數(shù)。
現(xiàn)在我們清楚了,PHP在實例化一個對象時(實際上在實現(xiàn)接口,使用類常數(shù)或類中的靜態(tài)變量,調用類中的靜態(tài)方法時都會如此),首先會在系統(tǒng)中查找該類(或接口)是否存在,如果不存在的話就嘗試使用autoload機制來加載該類。而autoload機制的主要執(zhí)行過程為:
檢查執(zhí)行器全局變量函數(shù)指針autoload_func是否為NULL。
如果autoload_func==NULL, 則查找系統(tǒng)中是否定義有__autoload()函數(shù),如果沒有,則報告錯誤并退出。
如果定義了__autoload()函數(shù),則執(zhí)行__autoload()嘗試加載類,并返回加載結果。
如果autoload_func不為NULL,則直接執(zhí)行autoload_func指針指向的函數(shù)用來加載類。注意此時并不檢查__autoload()函數(shù)是否定義。
真相終于大白,PHP提供了兩種方法來實現(xiàn)自動裝載機制,一種我們前面已經(jīng)提到過,是使用用戶定義的__autoload()函數(shù),這通常在PHP源程序中來實現(xiàn);另外一種就是設計一個函數(shù),將autoload_func指針指向它,這通常使用C語言在PHP擴展中實現(xiàn)。如果既實現(xiàn)了__autoload()函數(shù),又實現(xiàn)了autoload_func(將autoload_func指向某一PHP函數(shù)),那么只執(zhí)行autoload_func函數(shù)。
3. SPL autoload 機制的實現(xiàn)SPL是Standard PHP Library(標準PHP庫)的縮寫。它是PHP5引入的一個擴展庫,其主要功能包括autoload機制的實現(xiàn)及包括各種Iterator接口或類。SPL autoload機制的實現(xiàn)是通過將函數(shù)指針autoload_func指向自己實現(xiàn)的具有自動裝載功能的函數(shù)來實現(xiàn)的。SPL有兩個不同的函數(shù)spl_autoload, spl_autoload_call,通過將autoload_func指向這兩個不同的函數(shù)地址來實現(xiàn)不同的自動加載機制。
spl_autoload是SPL實現(xiàn)的默認的自動加載函數(shù),它的功能比較簡單。它可以接收兩個參數(shù),第一個參數(shù)是$class_name,表示類名,第二個參數(shù)$file_extensions是可選的,表示類文件的擴展名,可以在$file_extensions中指定多個擴展名,護展名之間用分號隔開即可;如果不指定的話,它將使用默認的擴展名.inc或.php。spl_autoload首先將$class_name變?yōu)樾?,然后在所有的include path中搜索$class_name.inc或$class_name.php文件(如果不指定$file_extensions參數(shù)的話),如果找到,就加載該類文件。你可以手動使用spl_autoload(”Person”, “.class.php”)來加載Person類。實際上,它跟require/include差不多,不同的它可以指定多個擴展名。
怎樣讓spl_autoload自動起作用呢,也就是將autoload_func指向spl_autoload?答案是使用spl_autoload_register函數(shù)。在PHP腳本中第一次調用spl_autoload_register()時不使用任何參數(shù),就可以將autoload_func指向spl_autoload。
通過上面的說明我們知道,spl_autoload的功能比較簡單,而且它是在SPL擴展中實現(xiàn)的,我們無法擴充它的功能。如果想實現(xiàn)自己的更靈活的自動加載機制怎么辦呢?這時,spl_autoload_call函數(shù)閃亮登場了。
我們先看一下spl_autoload_call的實現(xiàn)有何奇妙之處。在SPL模塊內(nèi)部,有一個全局變量autoload_functions,它本質上是一個HashTable,不過我們可以將其簡單的看作一個鏈表,鏈表中的每一個元素都是一個函數(shù)指針,指向一個具有自動加載類功能的函數(shù)。spl_autoload_call本身的實現(xiàn)很簡單,只是簡單的按順序執(zhí)行這個鏈表中每個函數(shù),在每個函數(shù)執(zhí)行完成后都判斷一次需要的類是否已經(jīng)加載,如果加載成功就直接返回,不再繼續(xù)執(zhí)行鏈表中的其它函數(shù)。如果這個鏈表中所有的函數(shù)都執(zhí)行完成后類還沒有加載,spl_autoload_call就直接退出,并不向用戶報告錯誤。因此,使用了autoload機制,并不能保證類就一定能正確的自動加載,關鍵還是要看你的自動加載函數(shù)如何實現(xiàn)。
那么自動加載函數(shù)鏈表autoload_functions是誰來維護呢?就是前面提到的spl_autoload_register函數(shù)。它可以將用戶定義的自動加載函數(shù)注冊到這個鏈表中,并將autoload_func函數(shù)指針指向spl_autoload_call函數(shù)(注意有一種情況例外,具體是哪種情況留給大家思考)。我們也可以通過spl_autoload_unregister函數(shù)將已經(jīng)注冊的函數(shù)從autoload_functions鏈表中刪除。
上節(jié)說過,當autoload_func指針非空時,就不會自動執(zhí)行__autoload()函數(shù)了,現(xiàn)在autoload_func已經(jīng)指向了spl_autoload_call,如果我們還想讓__autoload()函數(shù)起作用應該怎么辦呢?當然還是使用spl_autoload_register(__autoload)調用將它注冊到autoload_functions鏈表中。
現(xiàn)在回到第一節(jié)最后的問題,我們有了解決方案:根據(jù)每個類庫不同的命名機制實現(xiàn)各自的自動加載函數(shù),然后使用spl_autoload_register分別將其注冊到SPL自動加載函數(shù)隊列中就可了。這樣我們就不用維護一個非常復雜的__autoload函數(shù)了。
4. autoload 效率問題及對策使用autoload機制時,很多人的第一反應就是使用autoload會降低系統(tǒng)效率,甚至有人干脆提議為了效率不要使用autoload。在我們了解了autoload實現(xiàn)的原理后,我們知道autoload機制本身并不是影響系統(tǒng)效率的原因,甚至它還有可能提高系統(tǒng)效率,因為它不會將不需要的類加載到系統(tǒng)中。
那么為什么很多人都有一個使用autoload會降低系統(tǒng)效率的印象呢?實際上,影響autoload機制效率本身恰恰是用戶設計的自動加載函數(shù)。如果它不能高效的將類名與實際的磁盤文件(注意,這里指實際的磁盤文件,而不僅僅是文件名)對應起來,系統(tǒng)將不得不做大量的文件是否存在(需要在每個include path中包含的路徑中去尋找)的判斷,而判斷文件是否存在需要做磁盤I/O操作,眾所周知磁盤I/O操作的效率很低,因此這才是使得autoload機制效率降低的罪魁禍首!
因此,我們在系統(tǒng)設計時,需要定義一套清晰的將類名與實際磁盤文件映射的機制。這個規(guī)則越簡單越明確,autoload機制的效率就越高。
結論:autoload機制并不是天然的效率低下,只有濫用autoload,設計不好的自動裝載函數(shù)才會導致其效率的降低。
詳解spl_autoload_register()函數(shù)
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/21857.html
摘要:本文將會介紹自起,直至中增加的新特征。棄用的功能以下幾個功能被棄用,若在配置文件中啟用,則會在運行時發(fā)出警告。該類的完整限定名是其中第一個反斜杠表示全局命名空間。目的在于模仿的不對,目的是為了讓發(fā)布應用程序更加方便。 截至目前(2014.2), PHP 的最新穩(wěn)定版本是 PHP5.5, 但有差不多一半的用戶仍在使用已經(jīng)不在維護 [注] 的 PHP5.2, 其余的一半用戶在使用 PHP5...
摘要:看到一篇不錯的博文,轉載過來,可以通過這個自動加載函數(shù)來理解的類自動加載原理。在了解這個函數(shù)之前先來看另一個函數(shù)。調用靜態(tài)方法另一種寫法小結實例化時會被自動觸發(fā)該函數(shù),如果沒有執(zhí)行的對象時,就會執(zhí)行該方法。 看到一篇不錯的博文,轉載過來,可以通過這個自動加載函數(shù)spl_autoload_register()來理解PHP的類自動加載原理。 在了解這個函數(shù)之前先來看另一個函數(shù):__auto...
摘要:前言在開始之前,歡迎關注我自己的博客這篇文章是對自動加載功能的一個總結,內(nèi)容涉及的自動加載功能的命名空間的與標準等內(nèi)容。要實現(xiàn)第一步,第二步的功能,必須在開發(fā)時約定類名與磁盤文件的映射方法,只有這樣我們才能根據(jù)類名找到它對應的磁盤文件。 前言 在開始之前,歡迎關注我自己的博客:www.leoyang90.cn 這篇文章是對PHP自動加載功能的一個總結,內(nèi)容涉及PHP的自動加載功能、P...
摘要:在了解這個函數(shù)之前先來看另一個函數(shù)。一這是一個自動加載函數(shù),在中,當我們實例化一個未定義的類時,就會觸發(fā)此函數(shù)。在中,由于沒有包含,在實例化時,自動調用函數(shù),參數(shù)的值即為類名,此時就被引進來了。 在了解這個函數(shù)之前先來看另一個函數(shù):__autoload。 一、__autoload 這是一個自動加載函數(shù),在PHP5中,當我們實例化一個未定義的類時,就會觸發(fā)此函數(shù)。看下面例子: pr...
摘要:索性讀一下它的源碼。行載入類載入類,這個類比較重要,實現(xiàn)了自動加載。注冊錯誤和異常處理機制加載慣例配置文件接下來我們看一下自動加載的實現(xiàn)方法。所以借助此函數(shù)可以達到自動加載。博客鏈接解讀源碼一自動加載 聽說 TP5 已經(jīng) RC4 了,曾經(jīng)在 RC3 的時候用它寫過一個小東西。官方說從 RC4 以后改動不是太大。索性讀一下它的源碼。然后順便記錄一下,如有錯漏,請路過大神多多指正! 入口 ...
閱讀 1192·2021-11-24 09:38
閱讀 2602·2021-09-27 14:00
閱讀 1163·2019-08-30 15:55
閱讀 1337·2019-08-30 14:16
閱讀 1490·2019-08-30 10:54
閱讀 2863·2019-08-28 17:58
閱讀 758·2019-08-26 13:22
閱讀 1231·2019-08-26 12:01