天天躁日日躁狠狠躁AV麻豆-天天躁人人躁人人躁狂躁-天天澡夜夜澡人人澡-天天影视香色欲综合网-国产成人女人在线视频观看-国产成人女人视频在线观看

PHP autoload與spl_autoload自動(dòng)加載機(jī)制的深入理解

php autoload機(jī)制詳解
(1) autoload機(jī)制概述
在使用php的OO模式開(kāi)發(fā)系統(tǒng)時(shí),通常大家習(xí)慣上將每個(gè)類(lèi)的實(shí)現(xiàn)都存放在一個(gè)單獨(dú)的文件里,這樣會(huì)很容易實(shí)現(xiàn)對(duì)類(lèi)進(jìn)行復(fù)用,同時(shí)將來(lái)維護(hù)時(shí)也很便利。這 也是OO設(shè)計(jì)的基本思想之一。在php5之前,如果需要使用一個(gè)類(lèi),只需要直接使用include/require將其包含進(jìn)來(lái)即可。下面是一個(gè)實(shí)際的例 子:
復(fù)制代碼 代碼如下:
/* Person.class.php */
<?php
class Person {
var $name, $age;
function __construct ($name, $age)
{
$this->name = $name;
$this->age = $age;
}
}
?>
/* no_autoload.php */
<?php
require_once (”P(pán)erson.class.php”);
$person = new Person(”Altair”, 6);
var_dump ($person);
?>

在這個(gè)例子中,no-autoload.php文件需要使用Person類(lèi),它使用了require_once將其包含,然后就可以直接使用Person類(lèi)來(lái)實(shí)例化一個(gè)對(duì)象。

但 隨著項(xiàng)目規(guī)模的不斷擴(kuò)大,使用這種方式會(huì)帶來(lái)一些隱含的問(wèn)題:如果一個(gè)php文件需要使用很多其它類(lèi),那么就需要很多的require/include語(yǔ) 句,這樣有可能會(huì)造成遺漏或者包含進(jìn)不必要的類(lèi)文件。如果大量的文件都需要使用其它的類(lèi),那么要保證每個(gè)文件都包含正確的類(lèi)文件肯定是一個(gè)噩夢(mèng)。

php5為這個(gè)問(wèn)題提供了一個(gè)解決方案,這就是類(lèi)的自動(dòng)裝載(autoload)機(jī)制。autoload機(jī)制可以使得php程序有可能在使用類(lèi)時(shí)才自動(dòng)包含類(lèi)文件,而不是一開(kāi)始就將所有的類(lèi)文件include進(jìn)來(lái),這種機(jī)制也稱(chēng)為lazy loading。
下面是使用autoload機(jī)制加載Person類(lèi)的例子:
復(fù)制代碼 代碼如下:
/* autoload.php */
<?php
function __autoload($classname) {
require_once ($classname . “class.php”);
}
$person = new Person(”Altair”, 6);
var_dump ($person);
?>

通常php5在使用一個(gè)類(lèi)時(shí),如果發(fā)現(xiàn)這個(gè)類(lèi)沒(méi)有加載,就會(huì)自動(dòng)運(yùn)行__autoload()函數(shù),在這個(gè)函數(shù)中我們可以加載需要使用的類(lèi)。在我們這個(gè)簡(jiǎn) 單的例子中,我們直接將類(lèi)名加上擴(kuò)展名" title="擴(kuò)展名">擴(kuò)展名”.class.php”構(gòu)成了類(lèi)文件名,然后使用require_once將其加載。從這個(gè)例子中,我們可以看出 autoload至少要做三件事情,第一件事是根據(jù)類(lèi)名確定類(lèi)文件名,第二件事是確定類(lèi)文件所在的磁盤(pán)路徑(在我們的例子是最簡(jiǎn)單的情況,類(lèi)與調(diào)用它們的 php程序文件在同一個(gè)文件夾下),第三件事是將類(lèi)從磁盤(pán)文件中加載到系統(tǒng)中。第三步最簡(jiǎn)單,只需要使用include/require即可。要實(shí)現(xiàn)第一 步,第二步的功能,必須在開(kāi)發(fā)時(shí)約定類(lèi)名與磁盤(pán)文件的映射方法,只有這樣我們才能根據(jù)類(lèi)名找到它對(duì)應(yīng)的磁盤(pán)文件。

因此,當(dāng)有大量的類(lèi)文件要包含的時(shí)候,我們只要確定相應(yīng)的規(guī)則,然后在__autoload()函數(shù)中,將類(lèi)名與實(shí)際的磁盤(pán)文件對(duì)應(yīng)起來(lái),就可以實(shí)現(xiàn) lazy loading的效果。從這里我們也可以看出__autoload()函數(shù)的實(shí)現(xiàn)中最重要的是類(lèi)名與實(shí)際的磁盤(pán)文件映射規(guī)則的實(shí)現(xiàn)。

但現(xiàn)在問(wèn)題來(lái)了,如果在一個(gè)系統(tǒng)的實(shí)現(xiàn)中,如果需要使用很多其它的類(lèi)庫(kù),這些類(lèi)庫(kù)可能是由不同的開(kāi)發(fā)人員編寫(xiě)的,其類(lèi)名與實(shí)際的磁盤(pán)文件的映射規(guī)則不盡相 同。這時(shí)如果要實(shí)現(xiàn)類(lèi)庫(kù)文件的自動(dòng)加載,就必須在__autoload()函數(shù)中將所有的映射規(guī)則全部實(shí)現(xiàn),這樣的話__autoload()函數(shù)有可能 會(huì)非常復(fù)雜,甚至無(wú)法實(shí)現(xiàn)。最后可能會(huì)導(dǎo)致__autoload()函數(shù)十分臃腫,這時(shí)即便能夠?qū)崿F(xiàn),也會(huì)給將來(lái)的維護(hù)和系統(tǒng)效率帶來(lái)很大的負(fù)面影響。在 這種情況下,難道就沒(méi)有更簡(jiǎn)單清晰的解決辦法了吧?答案當(dāng)然是:NO! 在看進(jìn)一步的解決方法之前,我們先來(lái)看一下php中的autoload機(jī)制是如何實(shí)現(xiàn)的。

(2) php的autoload機(jī)制的實(shí)現(xiàn)
我 們知道,php文件的執(zhí)行分為兩個(gè)獨(dú)立的過(guò)程,第一步是將php文件編譯成普通稱(chēng)之為OPCODE的字節(jié)碼序列(實(shí)際上是編譯成一個(gè)叫做 zend_op_array的字節(jié)數(shù)組),第二步是由一個(gè)虛擬機(jī)來(lái)執(zhí)行這些OPCODE。php的所有行為都是由這些OPCODE來(lái)實(shí)現(xiàn)的。因此,為了研 究php中autoload的實(shí)現(xiàn)機(jī)制,我們將autoload.php文件編譯成opcode,然后根據(jù)這些OPCODE來(lái)研究php在這過(guò)程中都做了 些什么:
/* autoload.php 編譯后的OPCODE列表,是使用作者開(kāi)發(fā)的OPDUMP工具
*/
復(fù)制代碼 代碼如下:
 <?php
 // require_once (”P(pán)erson.php”);

 function __autoload ($classname) {
0 NOP               
0 RECV                1
    if (!class_exists($classname)) {
1 SEND_VAR            !0
2 DO_FCALL            ‘class_exists' [extval:1]
3 BOOL_NOT            $0 =>RES[~1]    
4 JMPZ                ~1, ->8
     require_once ($classname. “.class.php”);
5 CONCAT              !0, ‘.class.php' =>RES[~2]    
6 INCLUDE_OR_EVAL     ~2, REQUIRE_ONCE
    }
7 JMP                 ->8
 }
8 RETURN              null

 $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

 var_dump ($p);
7 SEND_VAR            !0
8 DO_FCALL            ‘var_dump' [extval:1]
 ?>

在 autoload.php的第10行代碼中我們需要為類(lèi)Person實(shí)例化一個(gè)對(duì)象。因此autoload機(jī)制一定會(huì)在該行編譯后的opcode中有所體 現(xiàn)。從上面的第10行代碼生成的OPCODE中我們知道,在實(shí)例化對(duì)象Person時(shí),首先要執(zhí)行FETCH_CLASS指令。我們就從php對(duì) FETCH_CLASS指令的處理過(guò)程開(kāi)始我們的探索之旅。
通過(guò)查閱php的源代碼(我使用的是php 5.3alpha2版本)可以發(fā)現(xiàn)如下的調(diào)用序列:
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行)
在最后一步的調(diào)用之前,我們先看一下調(diào)用時(shí)的關(guān)鍵參數(shù):
/* 設(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í)行用戶(hù)在php程序中自定義的函數(shù)或者php本身的庫(kù)函數(shù)。zend_call_function有兩個(gè) 重要的指針形參數(shù)fcall_info, fcall_cache,它們分別指向兩個(gè)重要的結(jié)構(gòu),一個(gè)是zend_fcall_info, 另一個(gè)是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在實(shí)例化一個(gè) 對(duì)象時(shí)(實(shí)際上在實(shí)現(xiàn)接口,使用類(lèi)常數(shù)或類(lèi)中的靜態(tài)變量,調(diào)用類(lèi)中的靜態(tài)方法時(shí)都會(huì)如此),首先會(huì)在系統(tǒng)中查找該類(lèi)(或接口)是否存在,如果不存在的話就 嘗試使用autoload機(jī)制來(lái)加載該類(lèi)。而autoload機(jī)制的主要執(zhí)行過(guò)程為:
(1) 檢查執(zhí)行器全局變量函數(shù)指針autoload_func是否為NULL。
(2) 如果autoload_func==NULL, 則查找系統(tǒng)中是否定義有__autoload()函數(shù),如果沒(méi)有,則報(bào)告錯(cuò)誤并退出。
(3) 如果定義了__autoload()函數(shù),則執(zhí)行__autoload()嘗試加載類(lèi),并返回加載結(jié)果。
(4) 如果autoload_func不為NULL,則直接執(zhí)行autoload_func指針指向的函數(shù)用來(lái)加載類(lèi)。注意此時(shí)并不檢查_(kāi)_autoload()函數(shù)是否定義。
真 相終于大白,php提供了兩種方法來(lái)實(shí)現(xiàn)自動(dòng)裝載機(jī)制,一種我們前面已經(jīng)提到過(guò),是使用用戶(hù)定義的__autoload()函數(shù),這通常在php源程序中 來(lái)實(shí)現(xiàn);另外一種就是設(shè)計(jì)一個(gè)函數(shù),將autoload_func指針指向它,這通常使用C語(yǔ)言在php擴(kuò)展中實(shí)現(xiàn)。如果既實(shí)現(xiàn)了 __autoload()函數(shù),又實(shí)現(xiàn)了autoload_func(將autoload_func指向某一php函數(shù)),那么只執(zhí)行 autoload_func函數(shù)。

(3) SPL autoload機(jī)制的實(shí)現(xiàn)
SPL 是Standard php Library(標(biāo)準(zhǔn)php庫(kù))的縮寫(xiě)。它是php5引入的一個(gè)擴(kuò)展庫(kù),其主要功能包括autoload機(jī)制的實(shí)現(xiàn)及包括各種Iterator接口或類(lèi)。 SPL autoload機(jī)制的實(shí)現(xiàn)是通過(guò)將函數(shù)指針autoload_func指向自己實(shí)現(xiàn)的具有自動(dòng)裝載功能的函數(shù)來(lái)實(shí)現(xiàn)的。SPL有兩個(gè)不同的函數(shù) spl_autoload, spl_autoload_call,通過(guò)將autoload_func指向這兩個(gè)不同的函數(shù)地址來(lái)實(shí)現(xiàn)不同的自動(dòng)加載機(jī)制。

spl_autoload 是SPL實(shí)現(xiàn)的默認(rèn)的自動(dòng)加載函數(shù),它的功能比較簡(jiǎn)單。它可以接收兩個(gè)參數(shù),第一個(gè)參數(shù)是$class_name,表示類(lèi)名,第二個(gè)參 數(shù)$file_extensions是可選的,表示類(lèi)文件的擴(kuò)展名" title="擴(kuò)展名">擴(kuò)展名,可以在$file_extensions中指定多個(gè)擴(kuò)展名" title="擴(kuò)展名">擴(kuò)展名,護(hù)展名之間用分號(hào)隔開(kāi)即 可;如果不指定的話,它將使用默認(rèn)的擴(kuò)展名" title="擴(kuò)展名">擴(kuò)展名.inc或.php。spl_autoload首先將$class_name變?yōu)樾?xiě),然后在所有的 include path中搜索$class_name.inc或$class_name.php文件(如果不指定$file_extensions參數(shù)的話),如果找 到,就加載該類(lèi)文件。你可以手動(dòng)使用spl_autoload(”P(pán)erson”, “.class.php”)來(lái)加載Person類(lèi)。實(shí)際上,它跟require/include差不多,不同的它可以指定多個(gè)擴(kuò)展名" title="擴(kuò)展名">擴(kuò)展名。

怎 樣讓spl_autoload自動(dòng)起作用呢,也就是將autoload_func指向spl_autoload?答案是使用 spl_autoload_register函數(shù)。在php腳本中第一次調(diào)用spl_autoload_register()時(shí)不使用任何參數(shù),就可以將 autoload_func指向spl_autoload。

通過(guò)上面的說(shuō)明我們知道,spl_autoload的功能比較簡(jiǎn)單,而且它是在SPL擴(kuò)展中實(shí)現(xiàn)的,我們無(wú)法擴(kuò)充它的功能。如果想實(shí)現(xiàn)自己的更靈活的自動(dòng)加載機(jī)制怎么辦呢?這時(shí),spl_autoload_call函數(shù)閃亮登場(chǎng)了。
我 們先看一下spl_autoload_call的實(shí)現(xiàn)有何奇妙之處。在SPL模塊內(nèi)部,有一個(gè)全局變量autoload_functions,它本質(zhì)上是 一個(gè)HashTable,不過(guò)我們可以將其簡(jiǎn)單的看作一個(gè)鏈表,鏈表中的每一個(gè)元素都是一個(gè)函數(shù)指針,指向一個(gè)具有自動(dòng)加載類(lèi)功能的函數(shù)。 spl_autoload_call本身的實(shí)現(xiàn)很簡(jiǎn)單,只是簡(jiǎn)單的按順序執(zhí)行這個(gè)鏈表中每個(gè)函數(shù),在每個(gè)函數(shù)執(zhí)行完成后都判斷一次需要的類(lèi)是否已經(jīng)加載, 如果加載成功就直接返回,不再繼續(xù)執(zhí)行鏈表中的其它函數(shù)。如果這個(gè)鏈表中所有的函數(shù)都執(zhí)行完成后類(lèi)還沒(méi)有加載,spl_autoload_call就直接 退出,并不向用戶(hù)報(bào)告錯(cuò)誤。因此,使用了autoload機(jī)制,并不能保證類(lèi)就一定能正確的自動(dòng)加載,關(guān)鍵還是要看你的自動(dòng)加載函數(shù)如何實(shí)現(xiàn)。

那 么自動(dòng)加載函數(shù)鏈表autoload_functions是誰(shuí)來(lái)維護(hù)呢?就是前面提到的spl_autoload_register函數(shù)。它可以將用戶(hù)定 義的自動(dòng)加載函數(shù)注冊(cè)到這個(gè)鏈表中,并將autoload_func函數(shù)指針指向spl_autoload_call函數(shù)(注意有一種情況例外,具體是哪 種情況留給大家思考)。我們也可以通過(guò)spl_autoload_unregister函數(shù)將已經(jīng)注冊(cè)的函數(shù)從autoload_functions鏈表 中刪除。

上節(jié)說(shuō)過(guò),當(dāng)autoload_func指針?lè)强諘r(shí),就不會(huì)自動(dòng)執(zhí)行__autoload()函數(shù) 了,現(xiàn)在autoload_func已經(jīng)指向了spl_autoload_call,如果我們還想讓__autoload()函數(shù)起作用應(yīng)該怎么辦呢?當(dāng) 然還是使用spl_autoload_register(__autoload)調(diào)用將它注冊(cè)到autoload_functions鏈表中。

現(xiàn)在回到第一節(jié)最后的問(wèn)題,我們有了解決方案:根據(jù)每個(gè)類(lèi)庫(kù)不同的命名機(jī)制實(shí)現(xiàn)各自的自動(dòng)加載函數(shù),然后使用spl_autoload_register分別將其注冊(cè)到SPL自動(dòng)加載函數(shù)隊(duì)列中就可了。這樣我們就不用維護(hù)一個(gè)非常復(fù)雜的__autoload函數(shù)了。

(4) autoload效率問(wèn)題及對(duì)策
使 用autoload機(jī)制時(shí),很多人的第一反應(yīng)就是使用autoload會(huì)降低系統(tǒng)效率,甚至有人干脆提議為了效率不要使用autoload。在我們了解了 autoload實(shí)現(xiàn)的原理后,我們知道autoload機(jī)制本身并不是影響系統(tǒng)效率的原因,甚至它還有可能提高系統(tǒng)效率,因?yàn)樗粫?huì)將不需要的類(lèi)加載到 系統(tǒng)中。

那么為什么很多人都有一個(gè)使用autoload會(huì)降低系統(tǒng)效率的印象呢?實(shí)際上,影響autoload機(jī)制效率本身恰恰是用戶(hù)設(shè)計(jì)的自動(dòng)加載函數(shù)。如果它 不能高效的將類(lèi)名與實(shí)際的磁盤(pán)文件(注意,這里指實(shí)際的磁盤(pán)文件,而不僅僅是文件名)對(duì)應(yīng)起來(lái),系統(tǒng)將不得不做大量的文件是否存在(需要在每個(gè) include path中包含的路徑中去尋找)的判斷,而判斷文件是否存在需要做磁盤(pán)I/O操作,眾所周知磁盤(pán)I/O操作的效率很低,因此這才是使得autoload機(jī) 制效率降低的罪魁禍?zhǔn)?

因此,我們?cè)谙到y(tǒng)設(shè)計(jì)時(shí),需要定義一套清晰的將類(lèi)名與實(shí)際磁盤(pán)文件映射的機(jī)制。這個(gè)規(guī)則越簡(jiǎn)單越明確,autoload機(jī)制的效率就越高。

結(jié)論:autoload機(jī)制并不是天然的效率低下,只有濫用autoload,設(shè)計(jì)不好的自動(dòng)裝載函數(shù)才會(huì)導(dǎo)致其效率的降低。

php技術(shù)PHP autoload與spl_autoload自動(dòng)加載機(jī)制的深入理解,轉(zhuǎn)載需保留來(lái)源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 久久全国免费久久青青小草 | 一区二区中文字幕在线观看 | 好男人在线视频 | 久草在线精彩免费视频 | 农民下乡在线观看3 | 午夜伦yy44880影院 | 97精品少妇偷拍蜜桃AV | 九九久久国产 | 午夜视频在线网站 | 忘忧草在线社区WWW日本直播 | 国产欧美一区二区三区久久 | 在线色av | jiizzyou欧美喷液 | 熟女久久久久久久久久久 | 久青草国产在视频在线观看 | 黑吊大战白女出浆 | 亚洲成年男人的天堂网 | 语文老师扒开胸罩喂我奶 | 91免费精品国自产拍在线可以看 | 免费国产足恋网站 | 玖玖爱精品视频 | 羞羞一区二区三区四区片 | 99在线精品免费视频 | 最近日本字幕MV免费观看在线 | 亚洲欧美国产双大乳头 | 俄罗斯XBXBXB兽交 | 性色AV一区二区三区V视界影院 | 久久精品天天爽夜夜爽 | 欧美午夜不卡在线观看 | 在线看片成人免费视频 | 99视频在线免费 | 麻豆蜜桃国语精品无码视频 | 人妻系列合集 | 99午夜高清在线视频在观看 | 黑人特黄AA完整性大片 | 国产精片久久久久久婷婷 | 全是肉的高h短篇列车 | 日本无码欧美激情在线视频 | 色柚视频网站ww色 | 领导边摸边吃奶边做爽在线观看 | 国产午夜在线观看视频 |