PHP-自動加載原理分析與理解

本文主要理解什麼是自動加載,爲什麼需要自動加載,自動加載的前世今生。


PHP-自動加載【自我概念理解】

PHP的自動加載,常見於各種框架的自動加載功能,PHP規範中的PSR0和PSR4原則,Composer的自動加載功能等等,這些都爲我們的開發提供了很大的方便。

在PHP面向對象(OO)編程中,爲了方便管理,我們都會把一個類寫在一個單獨的文件中。項目開發中我們難免要加載各種各樣的類,如果我們需要一個類,就require/include一個文件,這樣做的後果是效率非常低且文件頭太過累贅。由於require/include的做法不利於大型軟件開發,也就自然而然的引入了自動加載的方法。命名空間與自動加載賦予了PHP新的活力。讓PHP更具最新的軟件工程思想。

PHP5.2版本之前主要是通過require或者include載入依賴的文件資源。當項目變大的時候一個文件要依賴多個類就得在代碼之前寫很多行require/include來引入。PHP5.2之後,提供了類的自動載入功能,通過語法糖(魔術方法)function __autoload($class){require __DIR__.'/'.$class.'php;},在源碼運行上下文中一旦調用的類不存在,則會自動調用這個函數,這個函數會把調用的類名自動匹配項目路徑資源類文件,然後自動載入。可以理解爲__autoload魔術方法是require/include的自動化。

PHP7.2完全棄用了__autoload魔術方法,取而代之的是升級版的自動化載入:在php5.3之後,官方提供了一個spl_autoload_register()函數來取代__autoload()函數。__autoload魔術方法存在一個缺陷,當項目工程需要多個框架來搭建時。每個框架都有可能載入相同的類文件。這樣也就產生了重複加載錯誤。也就是說__autoload不夠足夠的智能化,__autoload在運行上下文中也只能定義一次。spl_autoload_register()正是解決__autoload的缺陷而誕生的。【PHP5實現了類的自動加載(Autoload)功能,這個功能最初是通過PHP的一個魔術方法__autoload()實現的。後來,PHP擴展SPL(Standard PHP Library 標準PHP類庫)又實現了更強大的自動加載機制。】

 


 

自動加載原理分析【轉載:晨風99的博客】

PHP原始自動加載

//文件 B.php
<?php
    class B
    {
        public function echo_info()
        {
            echo "我是class B中的方法執行結果";
        }
    }
?>

//文件 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 自動加載【轉載:晨風99的博客】

首先,明確一點,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
<?php
    class B
    {
        public function echo_info()
        {
            echo "我是class B中的方法執行結果";
        }
    }
?>

//文件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 自動加載的整個過程。【轉載:晨風99的博客】

首先還是剛剛的小例子,假如把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() //註銷自動加載函數


————————————————
版權聲明:本節爲CSDN博主「晨風99」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_37356656/article/details/78642520

 


【注意】SPL自動加載需要注意的地方

因爲命名空間的規範,我們處理引用路徑時需要特別注意。命名空間增加了自動加載的路徑方便性,但同時需要處理一下命名空間帶來的路徑字符處理。當我們使用命名空間時:use語句。當前上下文中我們要創建的加載類名,其實就是命名空間名。也就是說,在處理require_once($file)時,$class正是命名空間。

我們看一下SPL自動加載時路徑的變化:

// sdk/MyPrint.php
namespace sdk;

class MyPrint
{
	private $codechar = "河南省開軟網絡科技有限公司:KaiRuanSoft";
	
	public function doprint()
	{
		echo $this->codechar;
	}
}
//index.php

use sdk\MyPrint;

function my_autoload($class)
{
	$file =__DIR__ . '\\' . $class . '.php';

	echo '<H1>引用命名空間後$class的真實值就是命名空間:'.$class.'</H1>';
	echo '<h1>這是$file存儲的的路徑:'.$file.'</H1>';
	
	if(is_file($file)) 
	{
	 	require_once($file);
	}
	else
	{
		throw new Exception('Load File Error!');
	}
}


try
{
	spl_autoload_register('my_autoload');
	
	$display = new MyPrint();
	$display->doprint();
}
catch(Throwable $e)
{
	echo '<p>'.$e->getMessage();
}

 

運行程序後的結果:
引用命名空間後$class的真實值就是命名空間:sdk\MyPrint
這是$file存儲的的路徑:E:\www\php\sdk\MyPrint.php
河南省開軟網絡科技有限公司:KaiRuanSoft

運行程序後,創建的自動加載類MyPrint()的類名已經通過命名空間替換爲:sdk\MyPrint。

所以我們在組合類文件真實路徑時是這樣的:$file =__DIR__ . '\\' . $class . '.php';

 

命名空間和自動加載的關係

命名空間採用use來區分類、方法的作用域。相同的類和方法不一定是相同的。說白了就是防止同名污染。自動加載是引用類。命名空間和自動加載是相輔相成達到引用類資源的目的。但命名空間和自動加載本身而言沒有半毛錢關係。use不會觸發自動加載,只有在new時才觸發自動加載。

 

 

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