php自動加載機制autoload

1、 autoload機制概述
在使用PHP的OO模式開發系統時,通常大家習慣上將每個類的實現都存放在一個單獨的文件裏,這樣會很容易實現對類進行復用,同時將來維護時也很便利。這也是OO設計的基本思想之一。在PHP5之前,如果需要使用一個類,只需要直接使用include/require將其包含進來即可。下面是一個實際的例子:

/* 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 (”Person.class.php”);
$person = new Person(”Altair”, 6);
var_dump ($person);
?>

在這個例子中,no-autoload.php文件需要使用Person類,它使用了require_once將其包含,然後就可以直接使用Person類來實例化一個對象。

但隨着項目規模的不斷擴大,使用這種方式會帶來一些隱含的問題:如果一個PHP文件需要使用很多其它類,那麼就需要很多的require/include語句,這樣有可能會造成遺漏或者包含進不必要的類文件。如果大量的文件都需要使用其它的類,那麼要保證每個文件都包含正確的類文件肯定是一個噩夢。

PHP5爲這個問題提供了一個解決方案,這就是類的自動裝載(autoload)機制。autoload機制可以使得PHP程序有可能在使用類時才自動包含類文件,而不是一開始就將所有的類文件include進來,這種機制也稱爲lazy loading。

下面是使用autoload機制加載Person類的例子:

/* autoload.php */
<?php
function __autoload($classname) {
  require_once ($classname . “class.php”);
}
$person = new Person(”Altair”, 6);
var_dump ($person);
?>
通常PHP5在使用一個類時,如果發現這個類沒有加載,就會自動運行__autoload()函數,在這個函數中我們可以加載需要使用的類。在我們這個簡單的例子中,我們直接將類名加上擴展名”.class.php”構成了類文件名,然後使用require_once將其加載。從這個例子中,我們可以看出autoload至少要做三件事情,第一件事是根據類名確定類文件名,第二件事是確定類文件所在的磁盤路徑(在我們的例子是最簡單的情況,類與調用它們的PHP程序文件在同一個文件夾下),第三件事是將類從磁盤文件中加載到系統中。第三步最簡單,只需要使用include/require即可。要實現第一步,第二步的功能,必須在開發時約定類名與磁盤文件的映射方法,只有這樣我們才能根據類名找到它對應的磁盤文件。

因此,當有大量的類文件要包含的時候,我們只要確定相應的規則,然後在__autoload()函數中,將類名與實際的磁盤文件對應起來,就可以實現lazy loading的效果。從這裏我們也可以看出__autoload()函數的實現中最重要的是類名與實際的磁盤文件映射規則的實現。
但現在問題來了,如果在一個系統的實現中,如果需要使用很多其它的類庫,這些類庫可能是由不同的開發人員編寫的,其類名與實際的磁盤文件的映射規則不盡相同。這時如果要實現類庫文件的自動加載,就必須在__autoload()函數中將所有的映射規則全部實現,這樣的話__autoload()函數有可能會非常複雜,甚至無法實現。最後可能會導致__autoload()函數十分臃腫,這時即便能夠實現,也會給將來的維護和系統效率帶來很大的負面影響。

首先看下PHP實例化對象的過程,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函數。

2、 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函數了。

最後上例子:

Loader.php

<?php
Class Loader{
	
	static function  loadClass($className){
		echo "Loader Loading class ".$className."<br/>";
		$fileName = $className.".php";
		if(strpos($className, "_")){
			$fileName = str_replace("_", DIRECTORY_SEPARATOR,$classNmae).".php";
		}
		if(is_file($fileName)){
			if(class_exists($className)){
				echo "class isExit";
				return;
			}
			return require_once $fileName;
		}else{
			printf (" Loader cant't  load class ".$fileName."<br/>");
		}
	}
	
	static function autoRegister(){
		//echo 'Trying to load ', $className, ' via ', __METHOD__, "()\n";
		spl_autoload_register('Loader::loadClass');
	}
	
}
Loader::autoRegister(); //註冊函數
Loader1::autoRegister();
Loader2::autoRegister();
var_dump(spl_autoload_functions());

$b = new B();
$b->test(); 

?>

Loader1.php

<?php Class Loader1{    static function  loadClass($className){   echo "Loader1 loading  class ".$_SERVER["DOCUMENT_ROOT"].$className."<br/>";   $fileName = $_SERVER["DOCUMENT_ROOT"]."com.teedry.test/".$className.".php";   echo $fileName;   if(strpos($className, "_")){    $fileName = str_replace("_", DIRECTORY_SEPARATOR,$classNmae).".php";   }   if(is_file($fileName)){    if(class_exists($className)){     echo "class isExit";     return;    }    return require_once $fileName;   }else{    print_r("Loader1 cant't  load class ".$fileName."<br/>");   }  }    static function autoRegister(){   //echo 'Trying to load ', $className, ' via ', __METHOD__, "()\n";   spl_autoload_register('Loader1::loadClass');  }   }

?>

 

B.php

<?php /**  * php中autoload自動加載機制  *  */  function __autoload($className){  echo "__autoload load \n";  require_once $className.".php";//制定文件與磁盤映射關係的規則 }   Class B{

   public function test(){     $a = new A();     $a->printValue();    }       }

require_once '../com.teedry.autoload/Loader1.php'; //當使用spl_autoload_register註冊自動加載函數鏈表時,__autoload會失效 Loader1::autoRegister(); (new B())->test(); ?>

 

 

輸出結果:

Loader Load class Loader1
Loader Load class Loader2

array (size=3)
  0 => 
    array (size=2)
      0 =>  'Loader' (length=6)
      1 =>  'loadClass' (length=9)
  1 => 
    array (size=2)
      0 =>  'Loader1' (length=7)
      1 =>  'loadClass' (length=9)
  2 => 
    array (size=2)
      0 =>  'Loader2' (length=7)
      1 =>  'loadClass' (length=9)

Loader Loading class B
Loader cant't load class B.php
Loader1 loading class D:/phpworkspace/testPHP/B
Loader cant't load class A.php
Loader1 loading class D:/phpworkspace/testPHP/A
D:/phpworkspace/testPHP/com.teedry.test/A.php

testtest

 

從上述結果看到自動加載函數鏈表中有兩個已註冊的方法,當使用B類,但找不到該類時,會按照鏈表循找,找到即停止,否則拋出異常。

當使用spl_autoload_register註冊自動加載函數鏈表時,__autoload會失效

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章