PHP-自動載入原理分析
說起PHP的自動載入,很多同學可能都會想到各種框架的自動載入功能,PHP規範中的PSR0和PSR4原則,Composer的自動載入功能等等,這些都為我們的開發提供了很大的方便。
那麼PHP自動載入的前因後果到底是什麼?PHP的內部原理又是怎麼樣的呢?接下來我就根據自己的理解進行一下分析總結:
為什麼會有自動載入?
在PHP面向物件(OO)程式設計中,為了方便管理,我們都會把一個類寫在一個單獨的檔案中,那麼如果想在A類中使用B類的功能,就需要把B類載入到A類。對於這樣的需求在最原始的時候,我們是通過require 和 include 語法實現的,這2種語法結果基本一樣,執行流程有一些區別,這裡不解釋。例如:
//檔案 B.php
<?php
class B{
public function echo_info(){
echo "我是class B中的方法執行結果";
}
}
?>
//檔案 A.php
<?php
require 'b.php';//include 'b.php';
class A{
public function test(){
$b_object = new B();
$b_object->echo_info();
}
}
$a_object = new A();
$a_oject->test();
?>
命令列輸入:#php a.php
輸出: “我是class B中的方法執行結果“
於是,PHP5實現了類的自動載入(Autoload)功能,這個功能最初是通過PHP的一個魔術方法__autoload()實現的。後來,PHP擴充套件SPL(Standard PHP Library 標準PHP類庫)又實現了更強大的自動載入機制。
PHP原始自動載入
首先,先介紹下__autoload()方法。還是剛剛的例子,使用__autoload()可以做如下修改:
//檔案 B.php 不做修改
//檔案 A.php
<?php
class A{
public function test (){
$b_object = new B();
$b_object->echo_info();
}
}
function __autoload($classname){
require $classname.'.php';//include 'b.php';
}
$a_object = new A();
$a_oject->test();
?>
命令列輸入:#php a.php
輸出: “我是class B中的方法執行結果“
我們在A檔案中加了一個函式:__autoload(),並且自己在函式中編寫了相應的引入方法,執行之後同樣得到了結果,沒有報錯。我們需要明確 __autoload()函式PHP在找不到類的時候會自動執行,但是PHP內部並沒有定義這個函式,這個函式需要開發著自己定義,並且編寫內部邏輯,PHP只負責在需要的時候自動呼叫執行。而且在呼叫的時候會自動傳人要載入的類名作為引數。
有了__autoload()函式,可以看出,如果我們現在需要引入100個其它檔案,只需要訂好一個規則,編寫一個函式就可以了。這比直接用require/inlude有了很大進步,但是同樣也有新的問題,在一個專案中,我們只能編寫一個__autoload()函式,如果專案比較大,載入每個檔案都使用同樣的規則顯然是不現實的,那麼我們可能就需要在__autoload()中編寫複雜的規則邏輯來滿足載入不同檔案的需求。這同樣會使得__autoload()函式變得複雜臃腫,難以維護管理。
於是,SPL(Standard PHP Library 標準PHP類庫)的自動載入機制就應時而生了。
SPL 自動載入
首先,明確一點,PHP在例項化一個物件時(實際上在實現介面,使用類常數或類中的靜態變數,呼叫類中的靜態方法時都會如此),首先會在系統中查詢該類(或介面)是否存在,如果不存在的話就嘗試使用autoload機制來載入該類。而autoload機制的主要執行過程為:
- 檢查執行器全域性變數函式指標autoload_func是否是NULL;
- 如果 autoload_func==NULL ,則查詢系統是否定義 __autoload() 函式,如果定義了,則執行並返回載入結果。如果沒有定義,則報錯並退出;
- 如果 autoload_func 不等於NULL,則直接執行 autoload_func 指向的函式載入類,此時並不檢查 __autoload() 函式是否定義。
通過對PHP自動載入流程的瞭解,可以看到PHP實際上提供了兩種方法來實現自動裝載機制:
- 一種我們前面已經提到過,是使用使用者定義的__autoload()函式,這通常在PHP源程式中來實現;
- 另外一種就是設計一個函式,將autoload_func指標指向它,這通常使用C語言在PHP擴充套件中實現,即 SPL autoload機制。
如果兩種方式都實現了,也就是 autoload_func 不等於NULL,程式只會執行第二種方式,__autoload() 函式是不會被執行的。
先看一個 SPL 自動載入例子:
B.php檔案不變
A.php
<?php
class A{
public function test(){
$b_object = new B();
$b_object->echo_info();
}
}
function __autoload($classname){
require $classname.'.php';//include 'b.php';
}
function my_autoload($classname){
require $classname.'.php';//include 'b.php';
echo 'my_autoload ';
}
spl_autoload_register('my_autoload');
$a_object = new A();
$a_object->test();
結果:my_autoload 我是class B中的方法執行結果
?>
在這個小例子,可以看到,通過 spl_autoload_register(’my_autoload’),實現了 當程式執行找不到類B時,會執行 自定義的 my_autoload()函式,載入B類。實際上 spl_autoload_register(’my_autoload’) 的作用就是 把autoload_func 指標指向 my_autoload()。現在,整個PHP 自動載入過程就明白了。
接下來我們詳細分析下 SPL 自動載入的整個過程。
首先還是剛剛的小例子,假如把spl_autoload_register(’my_autoload’) 改成 spl_autoload_register()不新增任何引數,B類能被載入嗎?答案是:YES。
為什麼呢?
因為SPL擴充套件內部自己定義了一個自動載入函式 spl_autoload(),實現了自動載入的功能,如果我們不定義自己的自動載入函式,並且程式裡寫了 spl_autoload_register()(如果不傳引數,必須是第一次執行才會有效)或者 spl_autoload_register(’spl_autoload’),那麼autoload_func 指標就會指向內部函式 spl_autoload()。程式執行的時候如果找不到相應類就會執行該自動載入函式。
那麼,SPL 是怎麼實現autoload_func 指標指向不同的函式呢?
原來,在SPL內部定義了 一個函式 spl_autoload_call() 和 一個全域性變數autoload_functions。autoload_functions本質上是一個HashTable,不過我們可以將其簡單的看作一個連結串列,連結串列中的每一個元素都是一個函式指標,指向一個具有自動載入類功能的函式。
spl_autoload_call()的作用就是按順序遍歷 autoload_functions,使得autoload_func指向每個自動載入函式,如果載入成功就停止,如果不成功就繼續遍歷下個自動載入函式,直到載入成功或者遍歷完所有的函式。
那麼,autoload_functions 這個列表是誰來維護的呢?就是 spl_autoload_register() 這個函式。我們說的自動載入函式的註冊,其實就是通過spl_autoload_register()把自動載入函式加入到 autoload_functions 列表。
到此為止,整個自動載入的流程就是分析結束了。
相關SPL自動載入函式:
spl_autoload_functions() //列印autoload_functions列表
spl_autoload_unregister() //登出自動載入函式