最近在學習composer,發現從接觸PHP到現在已經遇到了三種關於PHP中類的自動加載方式,這其中包括PHP自帶的類的自動加載方式、PHP的第三方的依賴管理工具composer的加載方式以及PHP的Yaf框架下的自動加載方式。本篇博客主要是針對PHP5自帶的加載方式進行詳細介紹,composer和Yaf下類的自動加載將在接下來的時間里分兩篇和大家一起學習。
1.手動加載方式
像C和C++等語言,在PHP中需要使用另一個文件中的相關的類、方法時,可以使用include、include_once、require或者require_once將所用的文件包含進工程里面。其中,四者的區別如下。
- include將套用一個文件,如果文件不存在,則給出一個提示,跳過繼續執行;
- include_once也是套用一個文件,但是只會套用一次,如果文件不存在,則繼續執行;
- require表示套用一個文件,如果文件不存在,則中斷程序的執行;
- require_once也是套用一個文件,且只會套用一次,如果文件不存在,則中斷程序的執行;
以上四種方式是需要什么文件的時候,手動在程序當中包含進文件。這在項目的規模比較小的時候,是可以的;但是隨着項目規模的擴大,要通過手動的方式加載每個文件所需要的類簡直是一場噩夢。
為了省事,在加載的時候可以通過set_include_path()設置加載的路徑,同樣也可以通過get_include_path()獲取加載的路徑。關於set_include_path()和get_include_path(),我也是剛剛接觸,這里只對set_include_path()作簡要的介紹,以后遇到問題再加以補充。
首先,set_include_path()是在腳本中動態的對php.ini中的include_path進行動態的修改,而這個include_path就是對include和require(下文中如果不進行特別的說明,include代表include和include_once,require代表着require和require_once)的路徑進行設置,或者說是預定義。假如,我們在一個main.php文件中需要使用projname/home/lib/mylib/test文件夾下的a.php、b.php、c.php......,如果沒有設置包含的路徑的話,那么寫成如下的形式:
1
2
3
4
5
6
|
< ? php
include
(
"projname/home/lib/mylib/test/a.php"
);
include
(
"projname/home/lib/mylib/test/b.php"
);
include
(
"projname/home/lib/mylib/test/c.php"
);
......
|
這樣,每個include都需要包含絕對路徑,顯得很麻煩。如果在需要被包含的文件之前加上set_include_path(“projname/home/lib/mylib/test”),那么就可以寫成如下所示的形式:
1
2
3
4
5
6
7
|
< ? php
set_include_path(
"projname/home/lib/mylib/test"
);
include
(
"a.php"
);
include
(
"b.php"
);
include
(
"c.php"
);
......
|
相比於第一種費時費力的寫法,第二種明顯省去了很多的時間,但是仍然是要將每個文件包含進來,只是簡化了包含的路徑而已。當然,上面所說的情況是所需要的文件都存在於一個文件夾中,如果文件存在於不同的文件夾中,那么可以添加多條的set_include_path()語句,此時如果include或者require中的文件包含的文件名在多個目錄下出現,那么只會包含最先出現在set_include_path目錄中的文件;如果所有的set_include_path指定的文件夾中都沒有對應的文件,而文件名恰好出現在當前的文件夾中,則直接包含當前目錄下的對應的文件。
get_include_path()函數只適用於獲取當前的包含路徑。
2._autoload和spl_autoload_register()自動加載方式
為了將雙手從類的加載方式中解放出來,在PHP5及以后的版本中提供了一個自動加載的機制---autoload。Autoload可以使類在確實被需要的情況下才會被加載進來,也就是所謂的lazy loading,而不是一開始就include或者require所有的類文件。其中PHP提供的自動加載機制又分為兩種---__autoload()以及spl_autoload_register()。
1). __autoload機制
在PHP5中運行程序的過程中,如果發現某一個類並沒有被包含進來,那么就會運行__autoload自動加載機制,將所需要的類加載進來。其寫法如下:
1
2
3
4
5
6
7
8
9
10
|
< ? php
public
function
__autoload(
$classname
) {
$fileName
=
$classname
.
"php"
;
if
(file_exist(
$fileName
)) {
require_once
(
"$fileName"
);
}
else
{
echo
$fileName
.
" doesn't exist!"
}
}
|
根據這個程序寫法,我們可以得到如下的結論:保證自動加載機制的的原則就是要使得類名和文件名具有一種對應關系,類名+后綴構成了這個類所在的文件的名字。如果這個文件確實存在,那么就根據$fileName將該類加載進來。如果文件不存在,則提示用戶,文件不存在。總的來說自動加載機制包括三個步驟:
- 根據類名確定文件名,也就是確定一種類名和文件名之間的統一對應規則;
- 根據文件名在磁盤上找到相應的對應文件(例子中是最簡單的情況,就是類與調用他們的PHP文件都在同一個目錄下);如果不在同一個目錄下,那么可以使用set_include_path()指定要加載的路徑;
- 將磁盤文件加載到文件系統中,這一步只是用一般的include和require包含相應的類文件;
__autoload()實現類的自動加載的原則就是:類名和文件名之間具有一種統一的對應關系,這是在一個系統中實現__autoload的關鍵所在。但是一個系統可能是有不同的人員所開發,如果在開發之前沒有約定統一的標准,則可能存在不同的對應規則,導致需要在__autoload()中實現多種加載規則,那么可能導致__autoload()函數非常的臃腫。為了解決這個這個問題,PHP還提供了一個自動加載機制---spl_autoload_register().
2). spl_autoload_register()機制
SPL是Standard PHP Library(標准PHP庫)的縮寫,是PHP5引入的一個擴展庫。SPL autoload是通過將函數指針autoload_func指向自動裝載函數實現的。SPL具有兩個不同的自動裝載函數,分別是spl_autoload和spl_autoload_call,通過將autoload_fun指向這兩個不同的加載函數地址可以實現不同的自動加載機制。
- spl_autoload
spl_autoload是SPL實現的默認的自動加載函數,是一個可以接受兩個參數的函數。其中第一個函數為$class_name,表示要加載的類名;第二個參數是$file_extension為可選參數,表示類文件的擴展名。$file_extension中可以指定多個擴展名,擴展名之間用分號隔開即可,不指定擴展名,則使用默認的擴展名.inc或者.php。spl_autoload首先將$class_name變為小寫,然后在所有的include_path中搜索$ class_name.inc或者$class_name.php文件。如果找到對應的文件,就加載對應的類。其實可以手動的使用spl_autoload("xxxx",".php")來實現xxxx類的加載。這其實和require/include差不多,但是,spl_autoload相對來說靈活一點,因為可以指定多個擴展名。
前面說到,spl_autoload_register中包含的函數指針autoload_func用於指定要使用的加載函數。那么,我們必須將對應的函數地址賦值給autoload_func,spl_autoload_register()正實現了給函數指針autoload_func賦值的功能。如果spl_autoload_register()函數中不含有任何的參數,則默認是將spl_autoload()賦值給autoload_func.
- spl_autoload_call
SPL模塊的內部其實還存在着一個autoload_functions,其本質上是一個哈希表,或者為了直觀的理解,我們將其想像成一個容器,里面的各個元素都是指向加載函數的指針。spl_autoload_call的實現機制其實也比較簡單,按照一定的順序遍歷這個容器,執行里面的函數指針指向的加載函數,每執行一個指針之后都會檢查所需要的類是否已經完成加載。如果完成了加載,則退出。否則繼續接着向下執行。如果執行完所有的加載函數之后,所需要的類仍然沒有完成加載,則spl_autoload_call()直接退出。這也就是說即使使用了autoload機制,也不一定能夠完成類的加載,其關鍵在於看你如何創建你的自動加載函數。
既然,存在一個autoload_functions,那么如何將創建的自動加載函數添加到其中呢?spl_autoload一樣,同樣使用spl_autoload_register()將加載函數注冊到autoload_functions中。當然可以通過spl_autoload_unregister()函數將已經注冊的函數從autoload_functions從哈希表中刪除。這和前面所寫過的工廠設計模式是一致的,詳見:http://www.cnblogs.com/yue-blog/p/5771352.html。
這里需要說明的一點是spl_autoload_register實現自動加載的順序。spl_autoload的自動加載順序為:首先判斷autoload_func是否為空,如果autoload_func為空,則查看是否定義了__autoload函數,如果沒有定義,則返回,並報錯;如果定義了__autoload()函數,則返回加載的結果。如果autoload_func不為空,直接執行autoload_func指針指向的函數,不會檢查__autoload是否定義。也就是說優先使用spl_autoload_register()注冊過的函數。
根據以上介紹,如果autoload_func為非空是就不能自動執行__autoload()函數了。如果想在使用spl_autoload_register()函數的情況下,依然可以使用__autoload()函數,則可以將__autoload函數通過spl_autoload_register()添加到哈希表中,即,spl_autoload_register(__autoload())。下面的代碼示例分別說明了如何注冊普通的方法和類的靜態公有方法。
普通函數的注冊方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<? php
/**
* @ 普通函數的調用方法,可以調用后綴名分別為.php和.class.php的類文件
*/
function
loadFielEndOfPhp(
$classname
) {
$fileName
=
$classname
.
".php"
;
if
(file_exist(
$fileName
)) {
require_once
(
"$fileName"
);
}
else
{
echo
$fileName
.
" doesn't exist!"
}
}
function
loadFielEndOfClassPhp(
$classname
) {
$fileName
=
$classname
.
".class.php"
;
if
(file_exist(
$fileName
)) {
require_once
(
"$fileName"
);
}
else
{
echo
$fileName
.
" doesn't exist!"
}
spl_autoload_register(
"loadFielEndOfPhp"
);
spl_autoload_register(
"loadFielEndOfClassPhp"
);
}
|
類中靜態的加載函數的注冊方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<? php
/**
* @ 類中靜態成員函數的調用方法,可調用后綴名為.php和.class.php的文件
*/
class
test {
public
static
function
loadFielEndOfPhp(
$classname
) {
$fileName
=
$classname
.
".php"
;
if
(file_exist(
$fileName
)) {
require_once
(
"$fileName"
);
}
else
{
echo
$fileName
.
" doesn't exist!"
}
}
public
static
function
loadFielEndOfClassPhp(
$classname
) {
$fileName
=
$classname
.
".class.php"
;
if
(file_exist(
$fileName
)) {
require_once
(
"$fileName"
);
}
else
{
echo
$fileName
.
" doesn't exist!"
}
}
spl_autoload_register(
array
(
"test"
,
"loadFielEndOfPhp"
));
//spl_autoload_register("test::loadFielEndOfPhp"); //上一行的另一種寫法,不是使用數組的形式完成注冊;
spl_autoload_register(
array
(
"test"
,
"loadFielEndOfClassPhp"
));
//spl_autoload_register("test::loadFielEndOfClassPhp"); //第三行的另一種寫法,不是使用數組的形式完成注冊;
}
|