本文主要是對PHP自動載入功能的一個總結,內容涉及PHP的自動載入功能、PHP的命名空間、PHP的PSR0與PSR4標準等內容。希望能幫助大家。
一、PHP自動載入功能
PHP自動載入功能的由來
在PHP開發過程中,如果希望從外部引入一個class,通常會使用include 和require 方法,去把定義這個class 的檔案包含進來。這個在小規模開發的時候,沒什麼大問題。但在大型的開發專案中,使用這種方式會帶來一些隱含的問題:如果一個PHP 檔案需要使用很多其它類,那麼就需要很多的require/include 語句,這樣有可能會造成遺漏或包含進不必要的類別文件。如果大量的文件都需要使用其它的類,那麼要保證每個文件都包含正確的類文件肯定是一個噩夢, 況且 require_once 的代價很大。
PHP5 為這個問題提供了一個解決方案,這就是類別的自動裝載 (autoload) 機制。 autoload 機制可以使得 PHP 程式有可能在使用類別時才自動包含類別文件,而不是一開始就將所有的類別文件 include 進來,這種機制也稱為 lazy loading。
總結起來,自動載入功能帶來了幾處優點:
使用類別之前無需include 或require
使用類別的時候才會require/include 文件,實作了lazy loading,避免了require/include 多餘檔案。
無需考慮引入類別的實際磁碟位址,實現了邏輯和實體檔案的分離。
如果想具體詳細的了解關於自動載入的功能,可以查看資料:
PHP的類別自動載入機制
PHP的autoload機制的實作解析
PHP自動載入函數__autoload()
通常PHP5 在使用一個類別時,如果發現這個類別沒有加載,就會自動運行_autoload() 函數,這個函數是我們在程式中自訂的,在這個函數中我們可以加載需要使用的類別。以下是個簡單的範例:
<span style="font-size: 14px;">function __autoload($classname) { require_once ($classname . "class.php"); <br>}<br></span>
在我們這個簡單的範例中,我們直接將類別名稱加上副檔名”.class.php” 構成了類別檔名,然後使用require_once 將其載入。從這個例子中,我們可以看出autoload 至少要做三件事:
#根據類別名稱決定類別檔案名稱;
確定類別檔案所在的磁碟路徑(在我們的例子是最簡單的情況,類別與呼叫它們的PHP程式檔案在同一個資料夾下);
將類別從磁碟檔案載入到系統中。
第三步驟最簡單,只需要使用 include/require 即可。要實現第一步,第二步的功能,必須在開發時約定類別名稱與磁碟檔案的映射方法,只有這樣我們才能根據類別名稱找到它對應的磁碟檔案。
當有大量的類別檔案要包含的時候,我們只要確定對應的規則,然後在__autoload() 函數中,將類別名稱與實際的磁碟檔案對應起來,就可以達到lazy loading 的效果。從這裡我們也可以看出 _autoload() 函數的實作中最重要的是類別名稱與實際的磁碟檔案映射規則的實作。
#如果在一個系統的實作中,如果需要使用很多其它的類別庫,這些類庫可能是由不同的開發人員編寫的,其類別名稱與實際的磁碟檔案的映射規則不盡相同。這時如果要實現類別庫檔案的自動加載,就必須在 __autoload() 函數中將所有的映射規則全部實現,這樣的話 autoload() 函數有可能會非常複雜,甚至無法實現。最後可能會導致 autoload() 函數十分臃腫,這時即便能夠實現,也會對將來的維護和系統效率帶來很大的負面影響。
那麼問題出現在哪裡呢?問題出現在 _autoload() 是全域函數只能定義一次,不夠靈活,所以所有的類別名稱與檔案名稱對應的邏輯規則都要在一個函數裡面實現,造成這個函數的臃腫。那麼如何來解決這個問題呢?答案就是使用一個 _autoload 呼叫堆疊,不同的映射關係寫到不同的 _autoload 函數中去,然後統一註冊統一管理,這個就是 PHP5 引入的 SPL Autoload。
SPL是 Standard PHP Library (標準PHP函式庫)的縮寫。它是 PHP5 引入的擴充庫,其主要功能包括 autoload 機制的實作及包括各種 Iterator 介面或類別。 SPL Autoload 具體有幾個函數:
spl_autoload_register:註冊__autoload()函數
#spl_autoload_unregister:登出已註冊的函數
spl_autoload_functions:傳回所有已註冊的函數
######spl_autoload :__autoload() 的預設實作######## ###########spl_autoload_extionsions: 註冊並傳回spl_autoload 函數所使用的預設檔案副檔名。 ##################### ###這幾個函數具體詳細用法可見 php中spl_autoload詳解#############簡單來說,spl_autoload 是SPL 自己的定義__autoload() 函數,功能很簡單,就是去註冊的目錄(由set_include_path 設定)找與$classname 同名的.php/.inc 檔案。當然,你也可以指定特定類型的文件,方法是註冊副檔名 (spl_autoload_extionsions)。 ######
而spl_autoload_register() 就是我們上面所說的autoload 呼叫堆疊,我們可以向這個函數註冊多個我們自己的_autoload() 函數,當PHP 找不到類別名稱時,PHP 就會調用這個堆疊,一個一個去呼叫自訂的_autoload() 函數,實作自動載入功能。如果我們不輸入這個函數任何參數,那麼就會註冊 spl_autoload() 函數。
好啦,PHP 自動載入的底層就是這些,註冊機制已經非常靈活,但還缺少什麼呢?我們上面說過,自動載入關鍵就是類別名稱和檔案的映射,這種映射關係不同框架有不同方法,非常靈活,但是過於靈活就會顯得雜亂,PHP 有專門對這種映射關係的規範,那就是PSR標準中PSR0 與PSR4。
不過在談PSR0 與PSR4 之前,我們還需要了解PHP 的命名空間的問題,因為這兩個標準其實針對的都不是類別名稱與目錄檔案的映射,而是命名空間與檔案的映射。為什麼會這樣呢?在我的理解中,規範的物件導向PHP 思想,命名空間在某種程度上算是類別名稱的別名,那麼為什麼要推出命名空間,命名空間的優點是什麼呢
要了解命名空間,先來看看 官方文件 中命名空間的介紹:
什麼是命名空間?從廣義來說,命名空間是一種封裝事物的方法。在很多地方都可以看到這種抽象概念。例如,在作業系統中目錄用來將相關檔案分組,而對於目錄中的檔案來說,它就扮演了命名空間的角色。具體舉個例子,檔案 foo.txt 可以同時在目錄 /home/greg 和 /home/other 中存在,但在同一個目錄中不能存在兩個 foo.txt 檔案。另外,在目錄 /home/greg 外存取 foo.txt 檔案時,我們必須將目錄名稱以及目錄分隔符號放在檔案名稱之前得到 /home/greg/foo.txt。這個原理應用在程式設計領域就是命名空間的概念。
在PHP中,命名空間用來解決在編寫類別庫或應用程式時創建可重複使用的程式碼如類別或函數時碰到的兩類問題:
1 使用者編寫的程式碼與PHP內部的類/函數/常數或第三方類別/函數/常數之間的名字衝突
2 為很長的識別符名稱(通常是為了緩解第一類問題而定義的)創建一個或簡短)的名稱,提高原始碼的可讀性。
PHP 命名空間提供了一種將相關的類別、函數和常數組合在一起的途徑。
簡單來說就是PHP 是不允許程式中存在兩個名字一樣一樣的類別或是函數名稱的,那麼有人就很疑惑了,那就不起一樣名字不就可以了?事實上很多大程式依賴很多第三方函式庫,名字衝突什麼的不要太常見,這個就是官網中的第一個問題。那麼如何解決這個問題呢?在沒有命名空間的時候,可憐的程式設計師只能給類別名稱起a_b_c_d_e_f 這樣的,其中a/b/c/d/e/f 一般有其特定意義,這樣一般就不會發生衝突了,但是這樣長的類別名稱寫起來累,讀起來更是難受。因此 PHP5 就推出了命名空間,類別名是類別名,命名空間是命名空間,程式寫 / 看的時候直接用類別名,運作起來機器看的是命名空間,這樣就解決了問題。
另外,命名空間提供了將相關的類別、函數和常數組合在一起的途徑。這也是物件導向語言命名空間的很大用途,把特定用途所需的類別、變數、函數寫到一個命名空間中,進行封裝。
解決了類別名稱的問題,我們終於可以回到 PSR 標準來了,那麼 PSR0 與 PSR4 是怎麼規範檔案與命名空間的映射關係的呢?答案就是:對命名空間的命名(額,有點繞)、類別檔案目錄的位置和兩者映射關係做出了限制,這個就是標準的核心了。更完整的描述可見 現代PHP 新特色系列(一) —— 命名空間
##在說PSR0 與PSR4 之前先介紹PSR 標準。 PSR 標準的發明者和規範者是:PHP-FIG,它的網址是:www.php-fig.org。就是這個聯盟組織發明並創造了 PSR 規範。 FIG 是Framework Interoperability Group(框架可互用性小組)的縮寫,由幾位開源框架的開發者成立於2009 年,從那開始也選取了許多其他成員進來,雖然不是「官方」 組織,但也代表了社區中不小的一塊。組織的目的在於:以最低程度的限制,來統一各個項目的編碼規範,避免各家自行發展的風格阻礙了程序設計師開發的困擾,於是大夥發明和總結了PSR,PSR 是Proposing a Standards Recommendation(提出標準建議)的縮寫。
具體詳細的規格標準可以查看 PHP中PSR規格
PRS-0規範是他們出的第1套規範,主要是製定了一些自動載入標準(Autoloading Standard)PSR-0 強制性要求幾點:
1、 一個完全合格的namespace 和class 必須符合這樣的結構:"52f0a8d2734c4b535654453e49349ac9(1db0f77747bb2a800b935b09d3ea2286)*f4e9db28b9ff9ba3f3e4b75ebb041388”
2、每個namespace 必須有一個頂層的namespace("Vendor Name" 提供者名字)
3、每個namespace 可以有多個子namespace
4、當從檔案系統載入時,每個namespace 的分隔符號(/)要轉換成DIRECTORY_SEPARATOR (作業系統路徑分隔符號)
5、在類別名稱中,每個底線(_)符號要轉換成DIRECTORY_SEPARATOR (作業系統路徑分隔符號) 。在 namespace 中,底線_符號是沒有(特殊)意義的。
6、從檔案系統載入時,合格的namespace 和class 一定是以.php 結尾的
7、verdor name,namespaces,class 名稱可以由大小寫字母組合而成(大小寫敏感的)
具體規則可能有些讓人暈,我們從頭講一下。
我們先來看PSR0標準大致內容,第1、2、3、7條對命名空間的名字做出了限制,第4、5條對命名空間和檔案目錄的映射關係做出了限制,第6條是檔案後綴名。
前面我們說過,PSR標準是如何規範命名空間和所在檔案目錄之間的映射關係?是透過限制命名空間的名字、所在檔案目錄的位置和兩者映射關係。
那麼我們可能就要問了,哪裡限制了檔案所在目錄的位置了呢?其實答案就是:
限制命名空間名字+ 限制命名空間名字與檔案目錄映射= 限製檔案目錄
#
好了,我們先想一想,對於一個具體程式來說,如果它想要支援PSR0標準,它需要做什麼調整呢?
首先,程式必須定義一個符合PSR0標準第4、5條的映射函數,然後把這個函數註冊到spl_register() 中;
其次,定義新的命名空間時,命名空間的名字和所在檔案的目錄位置必須符合第1、2、3、7條。
一般為了程式碼維護方便,我們會在一個檔案只定義一個命名空間。
好了,我們有了符合PSR0的命名空間的名字,透過符合PSR0標準的映射關係就可以得到符合PSR0標準的文件目錄地址,如果我們按照PSR0標準正確存放文件,就可以順利require該檔案了,我們就可以使用該命名空間啦,是不是很神奇呢?
接下來,我們詳細地來看看PSR0標準到底規範了什麼?
我們以 laravel 中第三方函式庫Symfony其中一個命名空間 /Symfony/Core/Request 為例,講一講上面 PSR0 標準。
一個完全合格的namespace 和class 必須符合這樣的結構:"52f0a8d2734c4b535654453e49349ac9(1db0f77747bb2a800b935b09d3ea2286)* f4e9db28b9ff9ba3f3e4b75ebb041388”
#上面所展示的/Symfony 就是Vendor Name,也就是第三方函式庫的名字,/Core 是Namespace 名字,一般是我們命名空間的一些屬性資訊(例如request 是Symfony 的核心功能);最後Request 就是我們命名空間的名字,這個標準規範就是讓人看到命名空間的來源、功能非常明朗,有利於程式碼的維護。
2 . 每個namespace 必須有一個頂層的namespace("Vendor Name" 提供者名字)
#也就是說每個命名空間都要有一個類似/Symfony 的頂級命名空間,為什麼要有這種規則呢?因為 PSR0 標準只負責頂級命名空間之後的映射關係,也就是 /Symfony/Core/Request 這一部分,關於 /Symfony 應該關聯到哪個目錄,那就是使用者或框架自己定義的了。所謂的頂層的 namespace,就是自訂了映射關係的命名空間,一般就是提供者名字(第三方函式庫的名字)。換句話說頂級命名空間是自動載入的基礎。為什麼標準要這麼設定?原因很簡單,如果有個命名空間是/Symfony/Core/Transport/Request,還有個命名空間是/Symfony/Core/Transport/Request1,如果沒有頂級命名空間,我們就得寫兩個路徑和這兩個命名空間相對應,如果再有Request2、Request3 呢。有了頂層命名空間 /Symfony,那我們就只需要一個目錄對應即可,剩下的就利用 PSR 標準去解析就行了。
3.每個namespace可以有多個子namespace
這很簡單,Request 可以定義成/Symfony/Core/Request,也可以定義成/Symfony/Core/Transport/Request,/Core 這個命名空間下面可以有很多子命名空間,放多少層命名空間都是自己定義。
#4.從檔案系統載入時,每個namespace 的分隔符號(/)要轉換成DIRECTORY_SEPARATOR (作業系統路徑分隔符號)
現在我們終於來到了映射規範了。命名空間的/符號要轉換成路徑分隔符,也就是說要把 /Symfony/Core/Request 這個命名空間轉為 SymfonyCoreRequest 這樣的目錄結構。
5.在類別名稱中,每個底線 _ 符號要轉換成 DIRECTORYSEPARATOR (作業系統路徑分隔符號)。在 namespace 中,底線符號是沒有(特殊)意義的。
這句話的意思是說,如果我們的命名空間是 /Symfony/Core/Request_a,那麼我們就應該把它對應到 SymfonyCoreRequesta 這樣的目錄。為什麼會有這種規定呢?這是因為 PHP5 之前並沒有命名空間,程式設計師只能把名字變成 Symfony_Core_Request_a 這樣, PSR0 的這條規定就是為了相容這種情況。
剩下兩個很簡單就不說了。
有這樣的命名空間命名規則和映射標準,我們就可以推理出我們應該把命名空間所在的檔案該放在哪裡了。依舊以Symfony/Core/Request 為例, 它的目錄是/path/to/project/vendor/Symfony/Core/Request.php,其中/path/to/project是你專案在磁碟的位置,/path/to /project/vendor 是專案用的所有第三方函式庫所在目錄。 /path/to/project/vendor/Symfony 就是與頂級命名空間 /Symfony 有對應關係的目錄,再往下的檔案目錄就是依照PSR0標準建立的:
/Symfony/Core/Request => /Symfony/Core/Request.php
一切都完成了嗎?不,還有一些瑕疵:
我們是否應該還相容於沒有命名空間的情況呢?
依照PSR0 標準,命名空間/A/B/C/D/E/F 必然對應一個目錄結構/A/B/C/D/ E/F,這種目錄結構層次是不是太深了?
#2013年底,新出了第5個規格-PSR-4 。
PSR-4規範如何指定檔案路徑從而自動載入類別定義,同時規範了自動載入檔案的位置。這個乍看之下和 PSR-0 重複了,實際上,在功能上確實有所重複。差別在於 PSR-4 的規格比較乾淨,去除了相容 PHP 5.3 先前版本的內容,有點 PSR-0 升級版的感覺。當然,PSR-4 也不是要完全取代 PSR-0,而是在必要的時候補充 PSR-0 ——當然,如果你願意,PSR-4 也可以取代 PSR-0。 PSR-4 可以和包括 PSR-0 在內的其他自動載入機制共同使用。
PSR4標準與PSR0標準的差異:
#在類別名稱中使用底線沒有任何特殊意義。
命名空間與檔案目錄的對應方法有所調整。
對第二項我們詳細解釋一下( Composer自動載入的原理):
假如我們有一個命名空間:Foo/class ,Foo 是頂級命名空間,其存在著使用者定義的與目錄的映射關係:
<span style="font-size: 14px;">"Foo/" => "src/"<br/></span>
按照PSR0标准,映射后的文件目录是: src/Foo/class.php,但是按照 PSR4 标准,映射后的文件目录就会是:src/class.php,为什么要这么更改呢?原因就是怕命名空间太长导致目录层次太深,使得命名空间和文件目录的映射关系更加灵活。
再举一个例子,来源 PSR-4——新鲜出炉的PHP规范:
PSR-0风格
<span style="font-size: 14px;"> vendor/<br> vendor_name/<br> package_name/<br> src/<br> Vendor_Name/<br> Package_Name/<br> ClassName.php # Vendor_Name\Package_Name\ClassName<br> tests/<br> Vendor_Name/<br> Package_Name/<br> ClassNameTest.php # Vendor_Name\Package_Name\ClassName<br></span>
PSR-4风格
<span style="font-size: 14px;"> vendor/<br> vendor_name/<br> package_name/<br> src/<br> ClassName.php # Vendor_Name\Package_Name\ClassName<br> tests/<br> ClassNameTest.php # Vendor_Name\Package_Name\ClassNameTest<br></span>
对比以上两种结构,明显可以看出PSR-4带来更简洁的文件结构。
以上是PHP實作自動載入功能的詳細內容。更多資訊請關注PHP中文網其他相關文章!