php 原生自動載入與載入規範
自動載入:在需要的時候才把類檔案載入進來,為此php內建了__autoload()
__autoload
function __autoload($classname) {
require_once ($classname . "class.php");
}
上面函式展示了一般自動載入要做的幾個事情:
- 根據類名確定類檔名;
- 確定類檔案所在的磁碟路徑(在我們的例子是最簡單的情況,類與呼叫它們的PHP程式檔案在同一個資料夾下);
- 將類從磁碟檔案中載入到系統中。
存在問題
問題出現在autoload() 是全域性函式只能定義一次,不夠靈活,所以所有的類名與檔名對應的邏輯規則都要在一個函式裡面實現,造成這個函式的臃腫。那麼如何來解決這個問題呢?答案就是使用一個 _autoload 呼叫堆疊,不同的對映關係寫到不同的 _autoload 函式中去,然後統一註冊統一管理,這個就是 PHP5 引入的 SPL Autoload。
SPL Autoload
其包含以下函式:
- spl_autoload_register:註冊 _autoload() 函式
- spl_autoload_unregister:登出已註冊的函式
- spl_autoload_functions:返回所有已註冊的函式
- spl_autoload_call:嘗試所有已註冊的函式來載入類
- spl_autoload :_autoload() 的預設實現
- spl_autoload_extionsions: 註冊並返回 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對這種對映關係做了規範,分別是psr-0和psr-4
psr-0 與 psr-4
PSR0標準
PRS-0規範是他們出的第1套規範,主要是制定了一些自動載入標準(Autoloading Standard)PSR-0強制性要求幾點:
- 1、 一個完全合格的 namespace 和 class 必須符合這樣的結構:“< Vendor Name>(< Namespace>)*< Class Name>”
- 2、每個 namespace 必須有一個頂層的namespace("Vendor Name"提供者名字)
- 3、每個 namespace 可以有多個子 namespace
- 4、當從檔案系統中載入時,每個 namespace 的分隔符(/)要轉換成 DIRECTORYSEPARATOR (作業系統路徑分隔符)
- 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 標準。
1 . 一個完全合格的 namespace 和 class 必須符合這樣的結構:“< Vendor Name>(< Namespace>)*< Class Name>”
上面所展示的 /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 這個名稱空間轉為 \Symfony\Core\Request 這樣的目錄結構。
5.在類名中,每個下劃線_符號要轉換成 DIRECTORYSEPARATOR (作業系統路徑分隔符)。在 namespace 中,下劃線\符號是沒有(特殊)意義的。
這句話的意思就是說,如果我們的名稱空間是 /Symfony/Core/Request_a,那麼我們就應該把它對映到 \Symfony\Core\Request\a 這樣的目錄。為什麼會有這種規定呢?這是因為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,這種目錄結構層次是不是太深了?
PSR4 標準
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 是頂級名稱空間,其存在著使用者定義的與目錄的對映關係:
"Foo/" => "src/"
按照 PSR0 標準,對映後的檔案目錄是: src/Foo/class.php,但是按照 PSR4 標準,對映後的檔案目錄就會是: src/class.php,為什麼要這麼更改呢?原因就是怕名稱空間太長導致目錄層次太深,使得名稱空間和檔案目錄的對映關係更加靈活。
sample
補充,頂級名稱空間與資料夾的對映是使用者自己或者框架(如laravel)定義好的,比如在laravel中,vender/composer/autoload_psr4.php中,有如下幾行程式碼定義了APP\這個頂級名稱空間對應的目錄為:專案根資料夾/app
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
// other class load...
'App\\' => array($baseDir . '/app'),
);
然後頂級名稱空間後的目錄解析就按照psr-4的規範進行解析了
即要先定義頂級名稱空間對應的目錄,然後再按照psr-4規範進行解析,最後載入進來需要用到的類檔案